1
0
mirror of https://github.com/gnss-sdr/gnss-sdr synced 2026-07-01 17:08:51 +00:00
Files
gnss-sdr/utils/python/hybrid_observables_plot_sample.py
T
2026-06-23 15:49:25 +02:00

193 lines
6.3 KiB
Python
Executable File

#!/usr/bin/env python3
"""
hybrid_observables_plot_sample.py
Reads a GNSS-SDR hybrid observables raw dump and plots pseudorange, carrier
phase, Doppler, and PRN values.
-----------------------------------------------------------------------------
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 pathlib import Path
import matplotlib.pyplot as plt
import numpy as np
from lib.dump_filename import resolve_dump_prefix
from lib.plot_format import add_output_format_argument, apply_publication_style
from lib.read_hybrid_observables_dump import read_hybrid_observables_dump
def parse_args():
parser = argparse.ArgumentParser(
description="Plot GNSS-SDR hybrid observables dump data."
)
parser.add_argument(
"-i",
"--input-path",
type=Path,
default=Path("."),
help="Directory containing the observables dump (default: .).",
)
parser.add_argument(
"--file-prefix",
default="observables.dat",
help="GNSS-SDR Observables.dump_filename value (default: "
"observables.dat). May include a directory and extension; the "
"matching <prefix>.dat file is read, resolved against --input-path.",
)
parser.add_argument(
"-o",
"--fig-path",
type=Path,
default=Path("plots/hybrid-observables"),
help="Directory where plots are saved.",
)
parser.add_argument(
"--channels",
type=int,
default=5,
help="Number of observable channels in the dump.",
)
parser.add_argument(
"--show",
action="store_true",
help="Display figures interactively after saving them.",
)
add_output_format_argument(parser)
return parser.parse_args()
def first_valid_observable(gnss_observables, channels):
min_tow_idx = None
obs_idx = 0
for channel in range(channels):
valid_indices = np.where(np.array(gnss_observables["valid"][channel]) > 0)[0]
if len(valid_indices) == 0:
continue
idx = valid_indices[0]
if min_tow_idx is None or idx < min_tow_idx:
min_tow_idx = idx
obs_idx = channel
if min_tow_idx is None:
raise ValueError("No valid observables found in the dump.")
return min_tow_idx, obs_idx
def save_figure(fig_path, name, show, output_format):
plt.tight_layout()
plt.savefig(fig_path / f"{name}.{output_format}")
# Close unless it will be shown; main() triggers a single plt.show() at the
# end. Avoids repeated show()/close() cycles, which can crash interactive
# matplotlib backends (e.g. macOS) on window close.
if not show:
plt.close()
def main():
args = parse_args()
args.fig_path.mkdir(parents=True, exist_ok=True)
apply_publication_style()
directory, base = resolve_dump_prefix(args.file_prefix, args.input_path)
observables_file = directory / f"{base}.dat"
gnss_observables = read_hybrid_observables_dump(args.channels, observables_file)
min_tow_idx, obs_idx = first_valid_observable(gnss_observables, args.channels)
time_start = gnss_observables["RX_time"][obs_idx][min_tow_idx] - 100
time_end = gnss_observables["RX_time"][obs_idx][-1] + 100
plt.figure()
plt.title("Pseudorange")
for channel in range(args.channels):
plt.scatter(
gnss_observables["RX_time"][channel][min_tow_idx:],
gnss_observables["Pseudorange_m"][channel][min_tow_idx:],
s=1,
label=f"Channel {channel}",
)
plt.xlim(time_start, time_end)
plt.grid(True)
plt.xlabel("TOW [s]")
plt.ylabel("Pseudorange [m]")
plt.legend()
plt.gcf().canvas.manager.set_window_title("Pseudorange.png")
save_figure(args.fig_path, "Pseudorange", args.show, args.output_format)
plt.figure()
plt.title("Carrier Phase")
for channel in range(args.channels):
plt.scatter(
gnss_observables["RX_time"][channel][min_tow_idx:],
gnss_observables["Carrier_phase_hz"][channel][min_tow_idx:],
s=1,
label=f"Channel {channel}",
)
plt.xlim(time_start, time_end)
plt.xlabel("TOW [s]")
plt.ylabel("Accumulated Carrier Phase [cycles]")
plt.grid(True)
plt.legend()
plt.gcf().canvas.manager.set_window_title("AccumulatedCarrierPhase.png")
save_figure(args.fig_path, "AccumulatedCarrierPhase", args.show, args.output_format)
plt.figure()
plt.title("Doppler Effect")
for channel in range(args.channels):
plt.scatter(
gnss_observables["RX_time"][channel][min_tow_idx:],
gnss_observables["Carrier_Doppler_hz"][channel][min_tow_idx:],
s=1,
label=f"Channel {channel}",
)
plt.xlim(time_start, time_end)
plt.xlabel("TOW [s]")
plt.ylabel("Doppler Frequency [Hz]")
plt.grid(True)
plt.legend()
plt.gcf().canvas.manager.set_window_title("DopplerFrequency.png")
save_figure(args.fig_path, "DopplerFrequency", args.show, args.output_format)
plt.figure()
plt.title("GNSS Channels captured")
for channel in range(args.channels):
label = "unknown"
for prn in gnss_observables["PRN"][channel][min_tow_idx:]:
if int(prn) != 0:
label = str(int(prn))
break
plt.scatter(
gnss_observables["RX_time"][channel][min_tow_idx:],
gnss_observables["PRN"][channel][min_tow_idx:],
s=1,
label=f"PRN {channel} = {label}",
)
plt.xlim(time_start, time_end)
plt.xlabel("TOW [s]")
plt.ylabel("PRN")
plt.grid(True)
plt.legend()
plt.gcf().canvas.manager.set_window_title("PRNs.png")
save_figure(args.fig_path, "PRNs", args.show, args.output_format)
# Show all saved figures with a single plt.show() to avoid the repeated
# show()/close() cycle that can crash interactive backends on macOS.
if args.show:
plt.show()
if __name__ == "__main__":
try:
main()
except (OSError, ValueError) as exc:
raise SystemExit(f"Error: {exc}")