mirror of
https://github.com/gnss-sdr/gnss-sdr
synced 2025-09-13 00:06:02 +00:00
feat: add plot scripts for the VTL dump data
This commit is contained in:

committed by
Carles Fernandez

parent
0ce37a4cb9
commit
3c85f7445b
@@ -32,7 +32,7 @@ Vtl_Core::Vtl_Core(const Pvt_Conf &conf)
|
|||||||
dump_filename(conf.vtl_dump_filename), // vtl dump file name
|
dump_filename(conf.vtl_dump_filename), // vtl dump file name
|
||||||
N_ch(conf.vtl_gps_channels + conf.vtl_gal_channels), // total allocated channels
|
N_ch(conf.vtl_gps_channels + conf.vtl_gal_channels), // total allocated channels
|
||||||
N_st((conf.vtl_gal_channels > 0) ? 9 : 8), // x, vx, y, vy, z, vz, b (gps), b (gal), d
|
N_st((conf.vtl_gal_channels > 0) ? 9 : 8), // x, vx, y, vy, z, vz, b (gps), b (gal), d
|
||||||
N_meas(N_ch * 2), // 3 measurements (pr, prr, pracc) per channel
|
N_meas(N_ch * 2), // 2 measurements (pr, prr) per channel
|
||||||
N_st_idx(arma::linspace<arma::uvec>(0, N_st - 1, N_st)),
|
N_st_idx(arma::linspace<arma::uvec>(0, N_st - 1, N_st)),
|
||||||
i_clkb_E(i_clkb_G + 1),
|
i_clkb_E(i_clkb_G + 1),
|
||||||
i_clkd((conf.vtl_gal_channels > 0) ? (i_clkb_G + 2) : (i_clkb_G + 1))
|
i_clkd((conf.vtl_gal_channels > 0) ? (i_clkb_G + 2) : (i_clkb_G + 1))
|
||||||
@@ -582,27 +582,27 @@ void Vtl_Core::saveVTLdata(const Vtl_Data &rtk_data)
|
|||||||
// ekf rx d
|
// ekf rx d
|
||||||
tmp_double = ekf_X(i_clkd);
|
tmp_double = ekf_X(i_clkd);
|
||||||
dump_file.write(reinterpret_cast<char *>(&tmp_double), sizeof(double));
|
dump_file.write(reinterpret_cast<char *>(&tmp_double), sizeof(double));
|
||||||
// rtk rx p
|
// rtklib rx p
|
||||||
tmp_double = rtk_data.rx_p(0);
|
tmp_double = rtk_data.rx_p(0);
|
||||||
dump_file.write(reinterpret_cast<char *>(&tmp_double), sizeof(double));
|
dump_file.write(reinterpret_cast<char *>(&tmp_double), sizeof(double));
|
||||||
tmp_double = rtk_data.rx_p(1);
|
tmp_double = rtk_data.rx_p(1);
|
||||||
dump_file.write(reinterpret_cast<char *>(&tmp_double), sizeof(double));
|
dump_file.write(reinterpret_cast<char *>(&tmp_double), sizeof(double));
|
||||||
tmp_double = rtk_data.rx_p(2);
|
tmp_double = rtk_data.rx_p(2);
|
||||||
dump_file.write(reinterpret_cast<char *>(&tmp_double), sizeof(double));
|
dump_file.write(reinterpret_cast<char *>(&tmp_double), sizeof(double));
|
||||||
// rtk rx v
|
// rtklib rx v
|
||||||
tmp_double = rtk_data.rx_v(0);
|
tmp_double = rtk_data.rx_v(0);
|
||||||
dump_file.write(reinterpret_cast<char *>(&tmp_double), sizeof(double));
|
dump_file.write(reinterpret_cast<char *>(&tmp_double), sizeof(double));
|
||||||
tmp_double = rtk_data.rx_v(1);
|
tmp_double = rtk_data.rx_v(1);
|
||||||
dump_file.write(reinterpret_cast<char *>(&tmp_double), sizeof(double));
|
dump_file.write(reinterpret_cast<char *>(&tmp_double), sizeof(double));
|
||||||
tmp_double = rtk_data.rx_v(2);
|
tmp_double = rtk_data.rx_v(2);
|
||||||
dump_file.write(reinterpret_cast<char *>(&tmp_double), sizeof(double));
|
dump_file.write(reinterpret_cast<char *>(&tmp_double), sizeof(double));
|
||||||
// rtk rx b gps
|
// rtklib rx b gps
|
||||||
tmp_double = rtk_data.rx_clk(0);
|
tmp_double = rtk_data.rx_clk(0);
|
||||||
dump_file.write(reinterpret_cast<char *>(&tmp_double), sizeof(double));
|
dump_file.write(reinterpret_cast<char *>(&tmp_double), sizeof(double));
|
||||||
// rtk rx b gal
|
// rtklib rx b gal
|
||||||
tmp_double = rtk_data.rx_clk(1);
|
tmp_double = rtk_data.rx_clk(1);
|
||||||
dump_file.write(reinterpret_cast<char *>(&tmp_double), sizeof(double));
|
dump_file.write(reinterpret_cast<char *>(&tmp_double), sizeof(double));
|
||||||
// rtk rx d
|
// rtklib rx d
|
||||||
tmp_double = rtk_data.rx_clk(2);
|
tmp_double = rtk_data.rx_clk(2);
|
||||||
dump_file.write(reinterpret_cast<char *>(&tmp_double), sizeof(double));
|
dump_file.write(reinterpret_cast<char *>(&tmp_double), sizeof(double));
|
||||||
|
|
||||||
@@ -640,25 +640,25 @@ void Vtl_Core::saveVTLdata(const Vtl_Data &rtk_data)
|
|||||||
dump_file.write(reinterpret_cast<char *>(&tmp_double), sizeof(double));
|
dump_file.write(reinterpret_cast<char *>(&tmp_double), sizeof(double));
|
||||||
}
|
}
|
||||||
|
|
||||||
// rtk code pseudorange
|
// observed pseudorange
|
||||||
for (int i = 0; i < N_ch; i++)
|
for (int i = 0; i < N_ch; i++)
|
||||||
{
|
{
|
||||||
tmp_double = rtk_data.obs_pr(i);
|
tmp_double = rtk_data.obs_pr(i);
|
||||||
dump_file.write(reinterpret_cast<char *>(&tmp_double), sizeof(double));
|
dump_file.write(reinterpret_cast<char *>(&tmp_double), sizeof(double));
|
||||||
}
|
}
|
||||||
// rtk code pseudorange rate
|
// observed pseudorange rate
|
||||||
for (int i = 0; i < N_ch; i++)
|
for (int i = 0; i < N_ch; i++)
|
||||||
{
|
{
|
||||||
tmp_double = rtk_data.obs_prr(i);
|
tmp_double = rtk_data.obs_prr(i);
|
||||||
dump_file.write(reinterpret_cast<char *>(&tmp_double), sizeof(double));
|
dump_file.write(reinterpret_cast<char *>(&tmp_double), sizeof(double));
|
||||||
}
|
}
|
||||||
// vtl code pseudorange
|
// computed pseudorange
|
||||||
for (int i = 0; i < N_ch; i++)
|
for (int i = 0; i < N_ch; i++)
|
||||||
{
|
{
|
||||||
tmp_double = ekf_comp_Z(i);
|
tmp_double = ekf_comp_Z(i);
|
||||||
dump_file.write(reinterpret_cast<char *>(&tmp_double), sizeof(double));
|
dump_file.write(reinterpret_cast<char *>(&tmp_double), sizeof(double));
|
||||||
}
|
}
|
||||||
// vtl code pseudorange rate
|
// computed pseudorange rate
|
||||||
for (int i = 0; i < N_ch; i++)
|
for (int i = 0; i < N_ch; i++)
|
||||||
{
|
{
|
||||||
tmp_double = ekf_comp_Z(i + N_ch);
|
tmp_double = ekf_comp_Z(i + N_ch);
|
||||||
|
331
utils/python/lib/plotVTL.py
Normal file
331
utils/python/lib/plotVTL.py
Normal file
@@ -0,0 +1,331 @@
|
|||||||
|
|
||||||
|
"""
|
||||||
|
plotVTL.py
|
||||||
|
|
||||||
|
Plotting module for GNSS-SDR Vector Tracking Loop (VTL) dumps.
|
||||||
|
|
||||||
|
Pedro Pereira, 2025. pereirapedro@gmail.com
|
||||||
|
|
||||||
|
Expected GNSS_vtl keys (from vtl_read_dump.py):
|
||||||
|
nepoch, receiver_time_s, vtl_dt_s,
|
||||||
|
VTL_RX_P_*_ECEF_m, VTL_RX_V_*_ECEF_ms, VTL_RX_CLK_B_GPS_m, VTL_RX_CLK_B_GAL_m, VTL_RX_CLK_D_ms,
|
||||||
|
RTKL_RX_P_*_ECEF_m, RTKL_RX_V_*_ECEF_ms, RTKL_RX_CLK_B_GPS_m, RTKL_RX_CLK_B_GAL_m, RTKL_RX_CLK_D_ms,
|
||||||
|
VTL_active_channels,
|
||||||
|
EKF_prefit_PR_m, EKF_prefit_PRR_ms, EKF_postfit_PR_m, EKF_postfit_PRR_ms,
|
||||||
|
EKF_meascov_PR, EKF_meascov_PRR, EKF_process_noise,
|
||||||
|
RTKL_observed_PR_m, RTKL_observed_PRR_ms, VTL_computed_PR_m, VTL_computed_PRR_ms,
|
||||||
|
VTL_code_freq_hz,
|
||||||
|
RTKL_SV_P_*_ECEF_m, RTKL_SV_CLK_B_m, RTKL_SV_CLK_D_ms, RTKL_topo_delay_m, RTKL_iono_delay_m, RTKL_code_bias_m
|
||||||
|
|
||||||
|
-----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
GNSS-SDR is a Global Navigation Satellite System software-defined receiver.
|
||||||
|
This file is part of GNSS-SDR.
|
||||||
|
|
||||||
|
Copyright (C) 2022 (see AUTHORS file for a list of contributors)
|
||||||
|
SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
|
-----------------------------------------------------------------------------
|
||||||
|
"""
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import os
|
||||||
|
from typing import Iterable, Dict, Any
|
||||||
|
|
||||||
|
import numpy as np
|
||||||
|
import matplotlib.pyplot as plt
|
||||||
|
|
||||||
|
|
||||||
|
SKIP_EPOCHS_DEFAULT = 10 # number of epochs to skip in plots
|
||||||
|
|
||||||
|
|
||||||
|
def _ensure_dir(path: str) -> None:
|
||||||
|
if not os.path.exists(path):
|
||||||
|
os.makedirs(path, exist_ok=True)
|
||||||
|
|
||||||
|
|
||||||
|
def _epochs(G: Dict[str, Any]) -> np.ndarray:
|
||||||
|
if "receiver_time_s" in G and len(G["receiver_time_s"]):
|
||||||
|
t = np.asarray(G["receiver_time_s"], dtype=float)
|
||||||
|
return t - t[0]
|
||||||
|
if "vtl_dt_s" in G and len(G["vtl_dt_s"]):
|
||||||
|
dt = float(np.asarray(G["vtl_dt_s"]).ravel()[0])
|
||||||
|
n = _infer_nepoch(G)
|
||||||
|
return np.arange(n) * dt
|
||||||
|
n = _infer_nepoch(G)
|
||||||
|
return np.arange(n)
|
||||||
|
|
||||||
|
|
||||||
|
def _infer_nepoch(G: Dict[str, Any]) -> int:
|
||||||
|
for k in ["receiver_time_s", "VTL_RX_P_X_ECEF_m", "RTKL_RX_P_X_ECEF_m"]:
|
||||||
|
if k in G and hasattr(G[k], "__len__"):
|
||||||
|
return len(G[k])
|
||||||
|
if "nepoch" in G:
|
||||||
|
try:
|
||||||
|
return int(G["nepoch"])
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
return 0
|
||||||
|
|
||||||
|
|
||||||
|
def _reshape_ch(x, n_epoch: int, n_ch: int) -> np.ndarray:
|
||||||
|
a = np.asarray(x, dtype=float).ravel()
|
||||||
|
if a.size == n_epoch * n_ch:
|
||||||
|
return a.reshape(n_epoch, n_ch)
|
||||||
|
if n_ch > 0 and a.size % n_ch == 0:
|
||||||
|
return a.reshape(a.size // n_ch, n_ch)
|
||||||
|
if n_ch > 0 and a.size > 0:
|
||||||
|
pad = (-a.size) % n_ch
|
||||||
|
a = np.concatenate([a, np.full(pad, np.nan)])
|
||||||
|
return a.reshape(-1, n_ch)
|
||||||
|
return a.reshape(-1, 1)
|
||||||
|
|
||||||
|
|
||||||
|
def _active_mask(G: Dict[str, Any], n_epoch: int, n_ch: int) -> np.ndarray:
|
||||||
|
if "VTL_active_channels" not in G:
|
||||||
|
return np.ones((n_epoch, n_ch), dtype=bool)
|
||||||
|
act = _reshape_ch(G["VTL_active_channels"], n_epoch, n_ch)
|
||||||
|
return act.astype(bool)
|
||||||
|
|
||||||
|
|
||||||
|
def _sanitize(y: np.ndarray) -> np.ndarray:
|
||||||
|
y = np.asarray(y, dtype=np.float64, order='C')
|
||||||
|
y[~np.isfinite(y)] = np.nan
|
||||||
|
return y
|
||||||
|
|
||||||
|
|
||||||
|
def plotVTL(G: Dict[str, Any],
|
||||||
|
settings: Dict[str, Any],
|
||||||
|
groups: Iterable[str] = ("pos","vel","clock","residuals","meas","sv","activity"),
|
||||||
|
save: bool = True,
|
||||||
|
show: bool = False,
|
||||||
|
skip_epochs: int = SKIP_EPOCHS_DEFAULT) -> Dict[str, Any]:
|
||||||
|
|
||||||
|
groups = set(groups)
|
||||||
|
if "all" in groups:
|
||||||
|
groups = {"pos","vel","clock","residuals","meas","sv","activity"}
|
||||||
|
|
||||||
|
n_ch = int(settings.get("numberOfChannels", 0))
|
||||||
|
n_epoch = _infer_nepoch(G)
|
||||||
|
t_full = _epochs(G)
|
||||||
|
|
||||||
|
# Skip initial epochs for plotting
|
||||||
|
start = min(max(skip_epochs, 0), len(t_full)) if len(t_full) else 0
|
||||||
|
t = t_full[start:] if len(t_full) else t_full
|
||||||
|
|
||||||
|
outdir = settings.get("fig_path", "./figs")
|
||||||
|
if save:
|
||||||
|
_ensure_dir(outdir)
|
||||||
|
|
||||||
|
figs: Dict[str, Any] = {}
|
||||||
|
|
||||||
|
# Determine channels that were active at least once
|
||||||
|
active_mask = _active_mask(G, n_epoch, n_ch) if (n_ch > 0 and n_epoch > 0) else np.zeros((0,0), dtype=bool)
|
||||||
|
active_any = active_mask.any(axis=0) if active_mask.size else np.array([], dtype=bool)
|
||||||
|
|
||||||
|
def _slice_1d(y):
|
||||||
|
y = _sanitize(y)
|
||||||
|
return y[start:start+len(t)] if len(y) >= start else y*0
|
||||||
|
|
||||||
|
def _slice_2d(M):
|
||||||
|
M = _reshape_ch(M, n_epoch, n_ch)
|
||||||
|
M = _sanitize(M)
|
||||||
|
return M[start:start+len(t), :] if M.shape[0] >= start else np.empty((0, M.shape[1]))
|
||||||
|
|
||||||
|
def _maybe_close(fig, is_aggregate: bool):
|
||||||
|
if not is_aggregate or not show:
|
||||||
|
plt.close(fig)
|
||||||
|
|
||||||
|
# ---------- Position (ECEF) & Errors ----------
|
||||||
|
if "pos" in groups:
|
||||||
|
has_gt = all(k in settings for k in ["gt_rx_p_x","gt_rx_p_y","gt_rx_p_z"])
|
||||||
|
f, axs = plt.subplots(2 if has_gt else 1, 3, figsize=(13, 6 if has_gt else 4), constrained_layout=True)
|
||||||
|
axs = np.atleast_2d(axs)
|
||||||
|
pos_keys = ["VTL_RX_P_X_ECEF_m","VTL_RX_P_Y_ECEF_m","VTL_RX_P_Z_ECEF_m"]
|
||||||
|
labels = ["X","Y","Z"]
|
||||||
|
# Top row: positions for BOTH RTKL and VTL
|
||||||
|
for i, k in enumerate(pos_keys):
|
||||||
|
if f"RTKL_RX_P_{labels[i]}_ECEF_m" in G:
|
||||||
|
axs[0, i].plot(t, _slice_1d(G.get(f"RTKL_RX_P_{labels[i]}_ECEF_m", [])), linestyle="--", label=f"RTKL {labels[i]}")
|
||||||
|
axs[0, i].plot(t, _slice_1d(G.get(k, [])), label=f"VTL {labels[i]}")
|
||||||
|
axs[0, i].set_title(f"ECEF {labels[i]}"); axs[0, i].set_xlabel("Time [s]"); axs[0, i].set_ylabel("m"); axs[0, i].grid(True, alpha=0.3)
|
||||||
|
axs[0, i].legend(loc="best")
|
||||||
|
# Bottom row: errors for BOTH RTKL and VTL
|
||||||
|
if has_gt:
|
||||||
|
gt = np.array([settings["gt_rx_p_x"], settings["gt_rx_p_y"], settings["gt_rx_p_z"]], dtype=float)
|
||||||
|
vtl_xyz = np.vstack([_slice_1d(G.get(k, [])) for k in pos_keys]).T
|
||||||
|
rtk_xyz = np.vstack([_slice_1d(G.get(f"RTKL_RX_P_{lab}_ECEF_m", [])) for lab in labels]).T \
|
||||||
|
if f"RTKL_RX_P_{labels[0]}_ECEF_m" in G else None
|
||||||
|
if vtl_xyz.shape[0] == len(t):
|
||||||
|
vtl_err = vtl_xyz - gt
|
||||||
|
for i, lab in enumerate(labels):
|
||||||
|
if rtk_xyz is not None and rtk_xyz.shape[0] == len(t):
|
||||||
|
rtk_err = rtk_xyz - gt
|
||||||
|
axs[1, i].plot(t, rtk_err[:, i], linestyle="--", label=f"RTKL-gt {lab}", zorder=2)
|
||||||
|
axs[1, i].plot(t, vtl_err[:, i], label=f"VTL-gt {lab}", zorder=3)
|
||||||
|
axs[1, i].set_title(f"Error {lab}"); axs[1, i].set_xlabel("Time [s]"); axs[1, i].set_ylabel("m"); axs[1, i].grid(True, alpha=0.3)
|
||||||
|
axs[1, i].legend(loc="best")
|
||||||
|
figs["pos"] = f
|
||||||
|
if save: f.savefig(os.path.join(outdir, "pos_ecef_and_errors.png"), dpi=150)
|
||||||
|
_maybe_close(f, is_aggregate=True)
|
||||||
|
|
||||||
|
# ---------- Velocity (ECEF) ----------
|
||||||
|
if "vel" in groups:
|
||||||
|
f, axs = plt.subplots(1, 3, figsize=(13, 4), constrained_layout=True)
|
||||||
|
vel_keys = ["VTL_RX_V_X_ECEF_ms","VTL_RX_V_Y_ECEF_ms","VTL_RX_V_Z_ECEF_ms"]
|
||||||
|
labels = ["X","Y","Z"]
|
||||||
|
for i, k in enumerate(vel_keys):
|
||||||
|
if f"RTKL_RX_V_{labels[i]}_ECEF_ms" in G:
|
||||||
|
axs[i].plot(t, _slice_1d(G.get(f"RTKL_RX_V_{labels[i]}_ECEF_ms", [])), linestyle="--", label=f"RTKL {labels[i]}")
|
||||||
|
axs[i].plot(t, _slice_1d(G.get(k, [])), label=f"VTL {labels[i]}")
|
||||||
|
axs[i].set_title(f"ECEF Velocity {labels[i]}"); axs[i].set_xlabel("Time [s]"); axs[i].set_ylabel("m/s")
|
||||||
|
axs[i].grid(True, alpha=0.3); axs[i].legend(loc="best")
|
||||||
|
figs["vel"] = f
|
||||||
|
if save: f.savefig(os.path.join(outdir, "vel_ecef.png"), dpi=150)
|
||||||
|
_maybe_close(f, is_aggregate=True)
|
||||||
|
|
||||||
|
# ---------- Clock (bias & drift) ----------
|
||||||
|
if "clock" in groups:
|
||||||
|
f, axs = plt.subplots(1, 3, figsize=(13, 4), constrained_layout=True)
|
||||||
|
if "RTKL_RX_CLK_B_GPS_m" in G:
|
||||||
|
axs[0].plot(t, _slice_1d(G.get("RTKL_RX_CLK_B_GPS_m", [])), linestyle="--", label="RTKL GPS bias")
|
||||||
|
axs[0].plot(t, _slice_1d(G.get("VTL_RX_CLK_B_GPS_m", [])), label="VTL GPS bias")
|
||||||
|
axs[0].set_title("Clock Bias (GPS) [m]"); axs[0].grid(True, alpha=0.3); axs[0].set_xlabel("Time [s]"); axs[0].legend(loc="best")
|
||||||
|
|
||||||
|
if "VTL_RX_CLK_B_GAL_m" in G and len(G["VTL_RX_CLK_B_GAL_m"]) == len(t_full):
|
||||||
|
if "RTKL_RX_CLK_B_GAL_m" in G:
|
||||||
|
axs[1].plot(t, _slice_1d(G.get("RTKL_RX_CLK_B_GAL_m", [])), linestyle="--", label="RTKL GAL bias")
|
||||||
|
axs[1].plot(t, _slice_1d(G.get("VTL_RX_CLK_B_GAL_m", [])), label="VTL GAL bias")
|
||||||
|
axs[1].set_title("Clock Bias (GAL) [m]"); axs[1].grid(True, alpha=0.3); axs[1].set_xlabel("Time [s]"); axs[1].legend(loc="best")
|
||||||
|
else:
|
||||||
|
axs[1].axis("off")
|
||||||
|
|
||||||
|
if "RTKL_RX_CLK_D_ms" in G:
|
||||||
|
axs[2].plot(t, _slice_1d(G.get("RTKL_RX_CLK_D_ms", [])), linestyle="--", label="RTKL drift")
|
||||||
|
axs[2].plot(t, _slice_1d(G.get("VTL_RX_CLK_D_ms", [])), label="VTL drift")
|
||||||
|
axs[2].set_title("Clock Drift [m/s]"); axs[2].grid(True, alpha=0.3); axs[2].set_xlabel("Time [s]"); axs[2].legend(loc="best")
|
||||||
|
|
||||||
|
figs["clock"] = f
|
||||||
|
if save: f.savefig(os.path.join(outdir, "clock.png"), dpi=150)
|
||||||
|
_maybe_close(f, is_aggregate=True)
|
||||||
|
|
||||||
|
# ---------- Satellite-related per-channel (active only) ----------
|
||||||
|
if "sv" in groups and n_ch > 0 and len(t) > 0:
|
||||||
|
svx = _slice_2d(G.get("RTKL_SV_P_X_ECEF_m", []))
|
||||||
|
svy = _slice_2d(G.get("RTKL_SV_P_Y_ECEF_m", []))
|
||||||
|
svz = _slice_2d(G.get("RTKL_SV_P_Z_ECEF_m", []))
|
||||||
|
sv_cb = _slice_2d(G.get("RTKL_SV_CLK_B_m", []))
|
||||||
|
cd_all = _slice_2d(G.get("RTKL_SV_CLK_D_ms", []))
|
||||||
|
topo = _slice_2d(G.get("RTKL_topo_delay_m", []))
|
||||||
|
iono = _slice_2d(G.get("RTKL_iono_delay_m", []))
|
||||||
|
cbias = _slice_2d(G.get("RTKL_code_bias_m", []))
|
||||||
|
|
||||||
|
sat_root = os.path.join(outdir, "satellites"); _ensure_dir(sat_root) if save else None
|
||||||
|
for ch in range(n_ch):
|
||||||
|
if ch >= len(active_any) or not active_any[ch]:
|
||||||
|
continue
|
||||||
|
|
||||||
|
# Coords
|
||||||
|
fx, axx = plt.subplots(1, 3, figsize=(13,4), constrained_layout=True)
|
||||||
|
for i, (arr, lab) in enumerate([(svx, "X"), (svy, "Y"), (svz, "Z")]):
|
||||||
|
y = arr[:, ch] if arr.shape[1] > ch else np.array([])
|
||||||
|
axx[i].plot(t, y); axx[i].set_title(f"SV {lab} (CH{ch:02d})")
|
||||||
|
axx[i].set_xlabel("Time [s]"); axx[i].set_ylabel("m"); axx[i].grid(True, alpha=0.3)
|
||||||
|
if save: fx.savefig(os.path.join(sat_root, f"sv_coords_CH{ch:02d}.png"), dpi=150)
|
||||||
|
plt.close(fx)
|
||||||
|
|
||||||
|
# Clock
|
||||||
|
fclk, axc = plt.subplots(1, 2, figsize=(10,4), constrained_layout=True)
|
||||||
|
cb = sv_cb[:, ch] if sv_cb.shape[1] > ch else np.array([])
|
||||||
|
cd = cd_all[:, ch] if cd_all.shape[1] > ch else np.array([])
|
||||||
|
axc[0].plot(t, cb, label="Bias"); axc[0].set_title(f"Clock bias (CH{ch:02d})"); axc[0].set_xlabel("Time [s]"); axc[0].set_ylabel("m"); axc[0].grid(True, alpha=0.3)
|
||||||
|
axc[1].plot(t, cd, label="Drift"); axc[1].set_title(f"Clock drift (CH{ch:02d})"); axc[1].set_xlabel("Time [s]"); axc[1].set_ylabel("m/s"); axc[1].grid(True, alpha=0.3)
|
||||||
|
if save: fclk.savefig(os.path.join(sat_root, f"sv_clock_CH{ch:02d}.png"), dpi=150)
|
||||||
|
plt.close(fclk)
|
||||||
|
|
||||||
|
# bias
|
||||||
|
fe, axe = plt.subplots(1, 1, figsize=(10,4), constrained_layout=True)
|
||||||
|
y_topo = topo[:, ch] if topo.shape[1] > ch else np.array([])
|
||||||
|
y_iono = iono[:, ch] if iono.shape[1] > ch else np.array([])
|
||||||
|
y_cb = cbias[:, ch] if cbias.shape[1] > ch else np.array([])
|
||||||
|
axe.plot(t, y_topo, label="Topo")
|
||||||
|
axe.plot(t, y_iono, label="Iono")
|
||||||
|
axe.plot(t, y_cb, label="Code bias", linestyle="--")
|
||||||
|
axe.set_title(f"Propagation bias (CH{ch:02d})"); axe.set_xlabel("Time [s]"); axe.set_ylabel("m"); axe.grid(True, alpha=0.3); axe.legend(loc="best")
|
||||||
|
if save: fe.savefig(os.path.join(sat_root, f"sv_bias_CH{ch:02d}.png"), dpi=150)
|
||||||
|
plt.close(fe)
|
||||||
|
|
||||||
|
# ---------- Channel activity ----------
|
||||||
|
if "activity" in groups and n_ch > 0 and len(t) > 0:
|
||||||
|
act_full = _reshape_ch(G.get("VTL_active_channels", []), n_epoch, n_ch).astype(int)
|
||||||
|
act = act_full[start:start+len(t), :]
|
||||||
|
f, ax = plt.subplots(figsize=(12, 6), constrained_layout=True)
|
||||||
|
extent = (t[0], t[-1] if len(t) else 1, -0.5, n_ch - 0.5)
|
||||||
|
ax.imshow(act.T, aspect="auto", interpolation="nearest", origin="lower", extent=extent)
|
||||||
|
ax.set_xlabel("Time [s]"); ax.set_ylabel("Channel")
|
||||||
|
ax.set_title("Channel Activity (1=active, 0=inactive)")
|
||||||
|
ax.set_yticks(range(n_ch)); ax.grid(False)
|
||||||
|
figs["activity"] = f
|
||||||
|
if save: f.savefig(os.path.join(outdir, "channel_activity.png"), dpi=150)
|
||||||
|
if not show: plt.close(f)
|
||||||
|
|
||||||
|
# ---------- Per-channel measurements (active channels only) ----------
|
||||||
|
if ("meas" in groups or "residuals" in groups) and n_ch > 0 and len(t) > 0:
|
||||||
|
meas_root = os.path.join(outdir, "measurements")
|
||||||
|
res_root = os.path.join(meas_root, "residuals")
|
||||||
|
if save:
|
||||||
|
_ensure_dir(meas_root); _ensure_dir(res_root)
|
||||||
|
|
||||||
|
PR_obs = _slice_2d(G.get("RTKL_observed_PR_m", []))
|
||||||
|
PR_comp = _slice_2d(G.get("VTL_computed_PR_m", []))
|
||||||
|
PRR_obs = _slice_2d(G.get("RTKL_observed_PRR_ms", []))
|
||||||
|
PRR_comp = _slice_2d(G.get("VTL_computed_PRR_ms", []))
|
||||||
|
|
||||||
|
PREF_PR = _slice_2d(G.get("EKF_prefit_PR_m", []))
|
||||||
|
POSTF_PR = _slice_2d(G.get("EKF_postfit_PR_m", []))
|
||||||
|
PREF_PRR = _slice_2d(G.get("EKF_prefit_PRR_ms", []))
|
||||||
|
POSTF_PRR = _slice_2d(G.get("EKF_postfit_PRR_ms", []))
|
||||||
|
|
||||||
|
for ch in range(n_ch):
|
||||||
|
if ch >= len(active_any) or not active_any[ch]:
|
||||||
|
continue
|
||||||
|
|
||||||
|
# observed vs computed
|
||||||
|
fmc, axmc = plt.subplots(1, 2, figsize=(12,4), constrained_layout=True)
|
||||||
|
y1 = PR_obs[:, ch] if PR_obs.shape[1] > ch else np.array([])
|
||||||
|
y2 = PR_comp[:, ch] if PR_comp.shape[1] > ch else np.array([])
|
||||||
|
axmc[0].plot(t, y1, label="Observed")
|
||||||
|
axmc[0].plot(t, y2, linestyle="--", label="Computed")
|
||||||
|
axmc[0].set_title(f"PR Observed vs Computed (CH{ch:02d})"); axmc[0].set_xlabel("Time [s]"); axmc[0].set_ylabel("m"); axmc[0].grid(True, alpha=0.3)
|
||||||
|
|
||||||
|
y1r = PRR_obs[:, ch] if PRR_obs.shape[1] > ch else np.array([])
|
||||||
|
y2r = PRR_comp[:, ch] if PRR_comp.shape[1] > ch else np.array([])
|
||||||
|
axmc[1].plot(t, y1r, label="Observed")
|
||||||
|
axmc[1].plot(t, y2r, linestyle="--", label="Computed")
|
||||||
|
axmc[1].set_title(f"PRR Observed vs Computed (CH{ch:02d})"); axmc[1].set_xlabel("Time [s]"); axmc[1].set_ylabel("m/s"); axmc[1].grid(True, alpha=0.3)
|
||||||
|
axmc[0].legend(loc="best")
|
||||||
|
if save: fmc.savefig(os.path.join(meas_root, f"observed_vs_computed_CH{ch:02d}.png"), dpi=150)
|
||||||
|
plt.close(fmc)
|
||||||
|
|
||||||
|
# residuals
|
||||||
|
fr, axr = plt.subplots(1, 2, figsize=(12,4), constrained_layout=True)
|
||||||
|
yp1 = PREF_PR[:, ch] if PREF_PR.shape[1] > ch else np.array([])
|
||||||
|
yp2 = POSTF_PR[:, ch] if POSTF_PR.shape[1] > ch else np.array([])
|
||||||
|
axr[0].plot(t, yp1, label="Prefit")
|
||||||
|
axr[0].plot(t, yp2, label="Postfit")
|
||||||
|
axr[0].set_title(f"PR Residuals (CH{ch:02d})"); axr[0].set_xlabel("Time [s]"); axr[0].set_ylabel("m"); axr[0].grid(True, alpha=0.3)
|
||||||
|
|
||||||
|
yrr1 = PREF_PRR[:, ch] if PREF_PRR.shape[1] > ch else np.array([])
|
||||||
|
yrr2 = POSTF_PRR[:, ch] if POSTF_PRR.shape[1] > ch else np.array([])
|
||||||
|
axr[1].plot(t, yrr1, label="Prefit")
|
||||||
|
axr[1].plot(t, yrr2, label="Postfit")
|
||||||
|
axr[1].set_title(f"PRR Residuals (CH{ch:02d})"); axr[1].set_xlabel("Time [s]"); axr[1].set_ylabel("m/s"); axr[1].grid(True, alpha=0.3)
|
||||||
|
axr[0].legend(loc="best")
|
||||||
|
if save: fr.savefig(os.path.join(res_root, f"residuals_CH{ch:02d}.png"), dpi=150)
|
||||||
|
plt.close(fr)
|
||||||
|
|
||||||
|
if show:
|
||||||
|
plt.show()
|
||||||
|
|
||||||
|
return figs
|
360
utils/python/lib/vtl_read_dump.py
Normal file
360
utils/python/lib/vtl_read_dump.py
Normal file
@@ -0,0 +1,360 @@
|
|||||||
|
"""
|
||||||
|
vtl_read_dump.py
|
||||||
|
vtl_read_dump (filename)
|
||||||
|
|
||||||
|
Read GNSS-SDR Vector Tracking dump binary file into Python.
|
||||||
|
Opens GNSS-SDR vector tracking binary log file .dat and returns the contents
|
||||||
|
|
||||||
|
Pedro Pereira, 2025. pereirapedro@gmail.com
|
||||||
|
|
||||||
|
Args:
|
||||||
|
settings: receiver settings.
|
||||||
|
|
||||||
|
Return:
|
||||||
|
GNSS_vtl: A dictionary with the processed data in lists
|
||||||
|
|
||||||
|
-----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
GNSS-SDR is a Global Navigation Satellite System software-defined receiver.
|
||||||
|
This file is part of GNSS-SDR.
|
||||||
|
|
||||||
|
Copyright (C) 2022 (see AUTHORS file for a list of contributors)
|
||||||
|
SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
|
-----------------------------------------------------------------------------
|
||||||
|
"""
|
||||||
|
|
||||||
|
import struct
|
||||||
|
|
||||||
|
|
||||||
|
def vtl_read_dump(settings):
|
||||||
|
|
||||||
|
v1 = []
|
||||||
|
v2 = []
|
||||||
|
v3 = []
|
||||||
|
v4 = []
|
||||||
|
v5 = []
|
||||||
|
v6 = []
|
||||||
|
v7 = []
|
||||||
|
v8 = []
|
||||||
|
v9 = []
|
||||||
|
v10= []
|
||||||
|
v11 = []
|
||||||
|
v12 = []
|
||||||
|
v13 = []
|
||||||
|
v14 = []
|
||||||
|
v15 = []
|
||||||
|
v16 = []
|
||||||
|
v17 = []
|
||||||
|
v18 = []
|
||||||
|
v19 = []
|
||||||
|
v20 = []
|
||||||
|
v21 = []
|
||||||
|
v22 = []
|
||||||
|
v23 = []
|
||||||
|
v24 = []
|
||||||
|
v25 = []
|
||||||
|
v26 = []
|
||||||
|
v27 = []
|
||||||
|
v28 = []
|
||||||
|
v29 = []
|
||||||
|
v30 = []
|
||||||
|
v31 = []
|
||||||
|
v32 = []
|
||||||
|
v33 = []
|
||||||
|
v34 = []
|
||||||
|
v35 = []
|
||||||
|
v36 = []
|
||||||
|
v37 = []
|
||||||
|
v38 = []
|
||||||
|
v39 = []
|
||||||
|
v40 = []
|
||||||
|
v41 = []
|
||||||
|
v42 = []
|
||||||
|
GNSS_vtl = {}
|
||||||
|
|
||||||
|
bytes_shift = 0
|
||||||
|
|
||||||
|
uint32_size_bytes = 4
|
||||||
|
double_size_bytes = 8
|
||||||
|
|
||||||
|
f = open(settings["dump_path"], 'rb')
|
||||||
|
if f is None:
|
||||||
|
return None
|
||||||
|
else:
|
||||||
|
while True:
|
||||||
|
f.seek(bytes_shift, 0)
|
||||||
|
# epoch number
|
||||||
|
v1.append(struct.unpack('I', f.read(uint32_size_bytes))[0])
|
||||||
|
bytes_shift += uint32_size_bytes
|
||||||
|
f.seek(bytes_shift, 0)
|
||||||
|
|
||||||
|
# receiver time
|
||||||
|
v2.append(struct.unpack('d', f.read(double_size_bytes))[0])
|
||||||
|
bytes_shift += double_size_bytes
|
||||||
|
f.seek(bytes_shift, 0)
|
||||||
|
|
||||||
|
# time difference between vtl epochs
|
||||||
|
v3.append(struct.unpack('d', f.read(double_size_bytes))[0])
|
||||||
|
bytes_shift += double_size_bytes
|
||||||
|
f.seek(bytes_shift, 0)
|
||||||
|
|
||||||
|
# VTL receiver position x-axis
|
||||||
|
v4.append(struct.unpack('d', f.read(double_size_bytes))[0])
|
||||||
|
bytes_shift += double_size_bytes
|
||||||
|
f.seek(bytes_shift, 0)
|
||||||
|
# VTL receiver position y-axis
|
||||||
|
v5.append(struct.unpack('d', f.read(double_size_bytes))[0])
|
||||||
|
bytes_shift += double_size_bytes
|
||||||
|
f.seek(bytes_shift, 0)
|
||||||
|
# VTL receiver position z-axis
|
||||||
|
v6.append(struct.unpack('d', f.read(double_size_bytes))[0])
|
||||||
|
bytes_shift += double_size_bytes
|
||||||
|
f.seek(bytes_shift, 0)
|
||||||
|
|
||||||
|
# VTL receiver velocity x-axis
|
||||||
|
v7.append(struct.unpack('d', f.read(double_size_bytes))[0])
|
||||||
|
bytes_shift += double_size_bytes
|
||||||
|
f.seek(bytes_shift, 0)
|
||||||
|
# VTL receiver velocity y-axis
|
||||||
|
v8.append(struct.unpack('d', f.read(double_size_bytes))[0])
|
||||||
|
bytes_shift += double_size_bytes
|
||||||
|
f.seek(bytes_shift, 0)
|
||||||
|
# VTL receiver velocity z-axis
|
||||||
|
v9.append(struct.unpack('d', f.read(double_size_bytes))[0])
|
||||||
|
bytes_shift += double_size_bytes
|
||||||
|
f.seek(bytes_shift, 0)
|
||||||
|
|
||||||
|
# VTL receiver clock bias (GPS)
|
||||||
|
v10.append(struct.unpack('d', f.read(double_size_bytes))[0])
|
||||||
|
bytes_shift += double_size_bytes
|
||||||
|
f.seek(bytes_shift, 0)
|
||||||
|
# VTL receiver clock bias (GAL)
|
||||||
|
if settings["GAL_en"] == 1:
|
||||||
|
v11.append(struct.unpack('d', f.read(double_size_bytes))[0])
|
||||||
|
bytes_shift += double_size_bytes
|
||||||
|
f.seek(bytes_shift, 0)
|
||||||
|
|
||||||
|
# VTL receiver clock drift
|
||||||
|
v12.append(struct.unpack('d', f.read(double_size_bytes))[0])
|
||||||
|
bytes_shift += double_size_bytes
|
||||||
|
f.seek(bytes_shift, 0)
|
||||||
|
|
||||||
|
# RTKL receiver position x-axis
|
||||||
|
v13.append(struct.unpack('d', f.read(double_size_bytes))[0])
|
||||||
|
bytes_shift += double_size_bytes
|
||||||
|
f.seek(bytes_shift, 0)
|
||||||
|
# RTKL receiver position y-axis
|
||||||
|
v14.append(struct.unpack('d', f.read(double_size_bytes))[0])
|
||||||
|
bytes_shift += double_size_bytes
|
||||||
|
f.seek(bytes_shift, 0)
|
||||||
|
# RTKL receiver position z-axis
|
||||||
|
v15.append(struct.unpack('d', f.read(double_size_bytes))[0])
|
||||||
|
bytes_shift += double_size_bytes
|
||||||
|
f.seek(bytes_shift, 0)
|
||||||
|
|
||||||
|
# RTKL receiver velocity x-axis
|
||||||
|
v16.append(struct.unpack('d', f.read(double_size_bytes))[0])
|
||||||
|
bytes_shift += double_size_bytes
|
||||||
|
f.seek(bytes_shift, 0)
|
||||||
|
# RTKL receiver velocity y-axis
|
||||||
|
v17.append(struct.unpack('d', f.read(double_size_bytes))[0])
|
||||||
|
bytes_shift += double_size_bytes
|
||||||
|
f.seek(bytes_shift, 0)
|
||||||
|
# RTKL receiver velocity z-axis
|
||||||
|
v18.append(struct.unpack('d', f.read(double_size_bytes))[0])
|
||||||
|
bytes_shift += double_size_bytes
|
||||||
|
f.seek(bytes_shift, 0)
|
||||||
|
|
||||||
|
# RTKL receiver clock bias (GPS)
|
||||||
|
v19.append(struct.unpack('d', f.read(double_size_bytes))[0])
|
||||||
|
bytes_shift += double_size_bytes
|
||||||
|
f.seek(bytes_shift, 0)
|
||||||
|
|
||||||
|
# RTKL receiver clock bias (GAL)
|
||||||
|
v20.append(struct.unpack('d', f.read(double_size_bytes))[0])
|
||||||
|
bytes_shift += double_size_bytes
|
||||||
|
f.seek(bytes_shift, 0)
|
||||||
|
|
||||||
|
# RTKL receiver clock drift
|
||||||
|
v21.append(struct.unpack('d', f.read(double_size_bytes))[0])
|
||||||
|
bytes_shift += double_size_bytes
|
||||||
|
f.seek(bytes_shift, 0)
|
||||||
|
|
||||||
|
# active channels
|
||||||
|
for _ in range(settings["numberOfChannels"]):
|
||||||
|
v22.append(struct.unpack('d', f.read(double_size_bytes))[0])
|
||||||
|
bytes_shift += double_size_bytes
|
||||||
|
f.seek(bytes_shift, 0)
|
||||||
|
|
||||||
|
# EKF pre-fit (pseudorange)
|
||||||
|
for _ in range(settings["numberOfChannels"]):
|
||||||
|
v23.append(struct.unpack('d', f.read(double_size_bytes))[0])
|
||||||
|
bytes_shift += double_size_bytes
|
||||||
|
f.seek(bytes_shift, 0)
|
||||||
|
|
||||||
|
# EKF pre-fit (pseudorange rate)
|
||||||
|
for _ in range(settings["numberOfChannels"]):
|
||||||
|
v24.append(struct.unpack('d', f.read(double_size_bytes))[0])
|
||||||
|
bytes_shift += double_size_bytes
|
||||||
|
f.seek(bytes_shift, 0)
|
||||||
|
|
||||||
|
# EKF post-fit (pseudorange)
|
||||||
|
for _ in range(settings["numberOfChannels"]):
|
||||||
|
v25.append(struct.unpack('d', f.read(double_size_bytes))[0])
|
||||||
|
bytes_shift += double_size_bytes
|
||||||
|
f.seek(bytes_shift, 0)
|
||||||
|
|
||||||
|
# EKF post-fit (pseudorange rate)
|
||||||
|
for _ in range(settings["numberOfChannels"]):
|
||||||
|
v26.append(struct.unpack('d', f.read(double_size_bytes))[0])
|
||||||
|
bytes_shift += double_size_bytes
|
||||||
|
f.seek(bytes_shift, 0)
|
||||||
|
|
||||||
|
# EKF measurement covariance (pseudorange)
|
||||||
|
for _ in range(settings["numberOfChannels"]):
|
||||||
|
v27.append(struct.unpack('d', f.read(double_size_bytes))[0])
|
||||||
|
bytes_shift += double_size_bytes
|
||||||
|
f.seek(bytes_shift, 0)
|
||||||
|
|
||||||
|
# EKF measurement covariance (pseudorange rate)
|
||||||
|
for _ in range(settings["numberOfChannels"]):
|
||||||
|
v28.append(struct.unpack('d', f.read(double_size_bytes))[0])
|
||||||
|
bytes_shift += double_size_bytes
|
||||||
|
f.seek(bytes_shift, 0)
|
||||||
|
|
||||||
|
# EKF process noise
|
||||||
|
for i in range(settings["numberOfStates"]):
|
||||||
|
v29.append(struct.unpack('d', f.read(double_size_bytes))[0])
|
||||||
|
bytes_shift += double_size_bytes
|
||||||
|
f.seek(bytes_shift, 0)
|
||||||
|
|
||||||
|
# observed pseudorange
|
||||||
|
for _ in range(settings["numberOfChannels"]):
|
||||||
|
v30.append(struct.unpack('d', f.read(double_size_bytes))[0])
|
||||||
|
bytes_shift += double_size_bytes
|
||||||
|
f.seek(bytes_shift, 0)
|
||||||
|
|
||||||
|
# observed pseudorange rate
|
||||||
|
for _ in range(settings["numberOfChannels"]):
|
||||||
|
v31.append(struct.unpack('d', f.read(double_size_bytes))[0])
|
||||||
|
bytes_shift += double_size_bytes
|
||||||
|
f.seek(bytes_shift, 0)
|
||||||
|
|
||||||
|
# computed pseudorange
|
||||||
|
for _ in range(settings["numberOfChannels"]):
|
||||||
|
v32.append(struct.unpack('d', f.read(double_size_bytes))[0])
|
||||||
|
bytes_shift += double_size_bytes
|
||||||
|
f.seek(bytes_shift, 0)
|
||||||
|
|
||||||
|
# computed pseudorange rate
|
||||||
|
for _ in range(settings["numberOfChannels"]):
|
||||||
|
v33.append(struct.unpack('d', f.read(double_size_bytes))[0])
|
||||||
|
bytes_shift += double_size_bytes
|
||||||
|
f.seek(bytes_shift, 0)
|
||||||
|
|
||||||
|
# vector tracking code frequency feedback
|
||||||
|
for _ in range(settings["numberOfChannels"]):
|
||||||
|
v34.append(struct.unpack('d', f.read(double_size_bytes))[0])
|
||||||
|
bytes_shift += double_size_bytes
|
||||||
|
f.seek(bytes_shift, 0)
|
||||||
|
|
||||||
|
# satellite position in x-axis
|
||||||
|
for _ in range(settings["numberOfChannels"]):
|
||||||
|
v35.append(struct.unpack('d', f.read(double_size_bytes))[0])
|
||||||
|
bytes_shift += double_size_bytes
|
||||||
|
f.seek(bytes_shift, 0)
|
||||||
|
|
||||||
|
# satellite position in y-axis
|
||||||
|
for _ in range(settings["numberOfChannels"]):
|
||||||
|
v36.append(struct.unpack('d', f.read(double_size_bytes))[0])
|
||||||
|
bytes_shift += double_size_bytes
|
||||||
|
f.seek(bytes_shift, 0)
|
||||||
|
|
||||||
|
# satellite position in z-axis
|
||||||
|
for _ in range(settings["numberOfChannels"]):
|
||||||
|
v37.append(struct.unpack('d', f.read(double_size_bytes))[0])
|
||||||
|
bytes_shift += double_size_bytes
|
||||||
|
f.seek(bytes_shift, 0)
|
||||||
|
|
||||||
|
# satellite clock bias
|
||||||
|
for _ in range(settings["numberOfChannels"]):
|
||||||
|
v38.append(struct.unpack('d', f.read(double_size_bytes))[0])
|
||||||
|
bytes_shift += double_size_bytes
|
||||||
|
f.seek(bytes_shift, 0)
|
||||||
|
|
||||||
|
# satellite clock drift
|
||||||
|
for _ in range(settings["numberOfChannels"]):
|
||||||
|
v39.append(struct.unpack('d', f.read(double_size_bytes))[0])
|
||||||
|
bytes_shift += double_size_bytes
|
||||||
|
f.seek(bytes_shift, 0)
|
||||||
|
|
||||||
|
# troposphere bias
|
||||||
|
for _ in range(settings["numberOfChannels"]):
|
||||||
|
v40.append(struct.unpack('d', f.read(double_size_bytes))[0])
|
||||||
|
bytes_shift += double_size_bytes
|
||||||
|
f.seek(bytes_shift, 0)
|
||||||
|
|
||||||
|
# ionosphere bias
|
||||||
|
for _ in range(settings["numberOfChannels"]):
|
||||||
|
v41.append(struct.unpack('d', f.read(double_size_bytes))[0])
|
||||||
|
bytes_shift += double_size_bytes
|
||||||
|
f.seek(bytes_shift, 0)
|
||||||
|
|
||||||
|
# satellite code bias
|
||||||
|
for _ in range(settings["numberOfChannels"]):
|
||||||
|
v42.append(struct.unpack('d', f.read(double_size_bytes))[0])
|
||||||
|
bytes_shift += double_size_bytes
|
||||||
|
f.seek(bytes_shift, 0)
|
||||||
|
|
||||||
|
# Check file
|
||||||
|
linea = f.readline()
|
||||||
|
if not linea:
|
||||||
|
break
|
||||||
|
|
||||||
|
f.close()
|
||||||
|
|
||||||
|
GNSS_vtl['nepoch'] = v1
|
||||||
|
GNSS_vtl['receiver_time_s'] = v2
|
||||||
|
GNSS_vtl['vtl_dt_s'] = v3
|
||||||
|
GNSS_vtl['VTL_RX_P_X_ECEF_m'] = v4
|
||||||
|
GNSS_vtl['VTL_RX_P_Y_ECEF_m'] = v5
|
||||||
|
GNSS_vtl['VTL_RX_P_Z_ECEF_m'] = v6
|
||||||
|
GNSS_vtl['VTL_RX_V_X_ECEF_ms'] = v7
|
||||||
|
GNSS_vtl['VTL_RX_V_Y_ECEF_ms'] = v8
|
||||||
|
GNSS_vtl['VTL_RX_V_Z_ECEF_ms'] = v9
|
||||||
|
GNSS_vtl['VTL_RX_CLK_B_GPS_m'] = v10
|
||||||
|
GNSS_vtl['VTL_RX_CLK_B_GAL_m'] = v11
|
||||||
|
GNSS_vtl['VTL_RX_CLK_D_ms'] = v12
|
||||||
|
GNSS_vtl['RTKL_RX_P_X_ECEF_m'] = v13
|
||||||
|
GNSS_vtl['RTKL_RX_P_Y_ECEF_m'] = v14
|
||||||
|
GNSS_vtl['RTKL_RX_P_Z_ECEF_m'] = v15
|
||||||
|
GNSS_vtl['RTKL_RX_V_X_ECEF_ms'] = v16
|
||||||
|
GNSS_vtl['RTKL_RX_V_Y_ECEF_ms'] = v17
|
||||||
|
GNSS_vtl['RTKL_RX_V_Z_ECEF_ms'] = v18
|
||||||
|
GNSS_vtl['RTKL_RX_CLK_B_GPS_m'] = v19
|
||||||
|
GNSS_vtl['RTKL_RX_CLK_B_GAL_m'] = v20
|
||||||
|
GNSS_vtl['RTKL_RX_CLK_D_ms'] = v21
|
||||||
|
GNSS_vtl['VTL_active_channels'] = v22
|
||||||
|
GNSS_vtl['EKF_prefit_PR_m'] = v23
|
||||||
|
GNSS_vtl['EKF_prefit_PRR_ms'] = v24
|
||||||
|
GNSS_vtl['EKF_postfit_PR_m'] = v25
|
||||||
|
GNSS_vtl['EKF_postfit_PRR_ms'] = v26
|
||||||
|
GNSS_vtl['EKF_meascov_PR'] = v27
|
||||||
|
GNSS_vtl['EKF_meascov_PRR'] = v28
|
||||||
|
GNSS_vtl['EKF_process_noise'] = v29
|
||||||
|
GNSS_vtl['RTKL_observed_PR_m'] = v30
|
||||||
|
GNSS_vtl['RTKL_observed_PRR_ms'] = v31
|
||||||
|
GNSS_vtl['VTL_computed_PR_m'] = v32
|
||||||
|
GNSS_vtl['VTL_computed_PRR_ms'] = v33
|
||||||
|
GNSS_vtl['VTL_code_freq_hz'] = v34
|
||||||
|
GNSS_vtl['RTKL_SV_P_X_ECEF_m'] = v35
|
||||||
|
GNSS_vtl['RTKL_SV_P_Y_ECEF_m'] = v36
|
||||||
|
GNSS_vtl['RTKL_SV_P_Z_ECEF_m'] = v37
|
||||||
|
GNSS_vtl['RTKL_SV_CLK_B_m'] = v38
|
||||||
|
GNSS_vtl['RTKL_SV_CLK_D_ms'] = v39
|
||||||
|
GNSS_vtl['RTKL_topo_delay_m'] = v40
|
||||||
|
GNSS_vtl['RTKL_iono_delay_m'] = v41
|
||||||
|
GNSS_vtl['RTKL_code_bias_m'] = v42
|
||||||
|
|
||||||
|
return GNSS_vtl
|
74
utils/python/vtl_plot_data.py
Normal file
74
utils/python/vtl_plot_data.py
Normal file
@@ -0,0 +1,74 @@
|
|||||||
|
|
||||||
|
"""
|
||||||
|
vtl_plot_data.py
|
||||||
|
|
||||||
|
CLI entry-point to read GNSS-SDR Tracking dump binary file using the provided function and
|
||||||
|
plot internal variables
|
||||||
|
|
||||||
|
Pedro Pereira, 2025. pereirapedro@gmail.com
|
||||||
|
|
||||||
|
Example
|
||||||
|
--------
|
||||||
|
python vtl_plot_data.py --dump /path/to/vtl_dump.dat --channels 20 \
|
||||||
|
--galileo 1 --gt 4729369.07 -705574.36 4207012.59 \
|
||||||
|
--fig-dir ./PLOTS --groups pos vel clock residuals meas sv activity \
|
||||||
|
--interactive --no-show
|
||||||
|
|
||||||
|
-----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
GNSS-SDR is a Global Navigation Satellite System software-defined receiver.
|
||||||
|
This file is part of GNSS-SDR.
|
||||||
|
|
||||||
|
Copyright (C) 2022 (see AUTHORS file for a list of contributors)
|
||||||
|
SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
|
-----------------------------------------------------------------------------
|
||||||
|
"""
|
||||||
|
|
||||||
|
import argparse
|
||||||
|
from typing import Dict, Any
|
||||||
|
from lib.vtl_read_dump import vtl_read_dump
|
||||||
|
from lib.plotVTL import plotVTL
|
||||||
|
|
||||||
|
|
||||||
|
def parse_args():
|
||||||
|
p = argparse.ArgumentParser(description="Plot GNSS-SDR VTL dump.")
|
||||||
|
p.add_argument("--dump", required=True, help="Path to vtl_dump.dat")
|
||||||
|
p.add_argument("--channels", type=int, required=True, help="Number of channels in the dump")
|
||||||
|
p.add_argument("--galileo", type=int, default=1, choices=[0,1], help="GAL enabled flag recorded in dump")
|
||||||
|
p.add_argument("--fig-dir", default="./PLOTS", help="Directory to save figures")
|
||||||
|
p.add_argument("--gt", type=float, nargs=3, metavar=("X","Y","Z"),
|
||||||
|
help="Ground-truth ECEF (m): X Y Z")
|
||||||
|
p.add_argument("--groups", nargs="+",
|
||||||
|
default=["pos","vel","clock","residuals","meas","sv","activity"],
|
||||||
|
help="Which groups to plot. Use any of: pos vel clock residuals meas sv activity all")
|
||||||
|
p.add_argument("--no-save", action="store_true", help="Do not save figures")
|
||||||
|
p.add_argument("--show", action="store_true", help="Show aggregate matplotlib figures")
|
||||||
|
return p.parse_args()
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
args = parse_args()
|
||||||
|
|
||||||
|
settings: Dict[str, Any] = {
|
||||||
|
"numberOfChannels": args.channels,
|
||||||
|
"numberOfStates": 8 + (1 if args.galileo == 1 else 0),
|
||||||
|
"GAL_en": args.galileo,
|
||||||
|
"dump_path": args.dump,
|
||||||
|
"fig_path": args.fig_dir,
|
||||||
|
}
|
||||||
|
if args.gt is not None:
|
||||||
|
settings["gt_rx_p_x"], settings["gt_rx_p_y"], settings["gt_rx_p_z"] = args.gt
|
||||||
|
|
||||||
|
# Read dump
|
||||||
|
G = vtl_read_dump(settings)
|
||||||
|
|
||||||
|
# Plot
|
||||||
|
plotVTL(G, settings,
|
||||||
|
groups=args.groups,
|
||||||
|
save=not args.no_save,
|
||||||
|
show=args.show)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
Reference in New Issue
Block a user