sistemata la visualizzazione con tutti i dati degli scostamenti
This commit is contained in:
parent
b58d7f1022
commit
19fae5309e
31
debug_brackets.py
Normal file
31
debug_brackets.py
Normal file
@ -0,0 +1,31 @@
|
||||
import json
|
||||
|
||||
data = json.load(open('archive_simulations/20251113_170236_scenario_dritto.json'))
|
||||
sim = data['simulation_results']['0']['simulated']
|
||||
real = data['simulation_results']['0']['real']
|
||||
|
||||
print(f"Total simulated states: {len(sim)}")
|
||||
print(f"Total real states: {len(real)}")
|
||||
print(f"Sim range: {sim[0][0]:.2f} to {sim[-1][0]:.2f}")
|
||||
print(f"Real range: {real[0][0]:.2f} to {real[-1][0]:.2f}")
|
||||
|
||||
# Check how many real states find brackets
|
||||
bracketed = 0
|
||||
first_bracketed_time = None
|
||||
last_bracketed_time = None
|
||||
|
||||
for real_state in real:
|
||||
real_ts = real_state[0]
|
||||
# Find bracketing points
|
||||
for i in range(len(sim) - 1):
|
||||
if sim[i][0] <= real_ts <= sim[i + 1][0]:
|
||||
bracketed += 1
|
||||
if first_bracketed_time is None:
|
||||
first_bracketed_time = real_ts
|
||||
last_bracketed_time = real_ts
|
||||
break
|
||||
|
||||
print(f"\nBracketed real states: {bracketed} / {len(real)} ({100*bracketed/len(real):.1f}%)")
|
||||
if first_bracketed_time and last_bracketed_time:
|
||||
print(f"Bracketed time range: {first_bracketed_time:.2f} to {last_bracketed_time:.2f}")
|
||||
print(f"Bracketed duration: {last_bracketed_time - first_bracketed_time:.2f}s")
|
||||
9
debug_targets.py
Normal file
9
debug_targets.py
Normal file
@ -0,0 +1,9 @@
|
||||
import json
|
||||
|
||||
data = json.load(open('archive_simulations/20251113_170236_scenario_dritto.json'))
|
||||
targets = list(data['simulation_results'].keys())
|
||||
print(f'Targets in file: {targets}')
|
||||
for t in targets:
|
||||
real_count = len(data['simulation_results'][t]['real'])
|
||||
sim_count = len(data['simulation_results'][t]['simulated'])
|
||||
print(f'Target {t}: {real_count} real states, {sim_count} simulated states')
|
||||
@ -30,7 +30,10 @@ def main():
|
||||
"""Initializes and runs the application."""
|
||||
import logging
|
||||
|
||||
# Silence verbose loggers from third-party libraries
|
||||
logging.getLogger("matplotlib").setLevel(logging.WARNING)
|
||||
logging.getLogger("PIL").setLevel(logging.INFO)
|
||||
logging.getLogger("PIL.PngImagePlugin").setLevel(logging.INFO)
|
||||
|
||||
app = MainView()
|
||||
|
||||
|
||||
@ -37,6 +37,7 @@ class AnalysisWindow(tk.Toplevel):
|
||||
# State variables
|
||||
self.selected_target_id = tk.IntVar(value=0)
|
||||
self._active = True
|
||||
self._filtered_errors = None # Cache for spike-filtered errors used in statistics
|
||||
|
||||
# Carica i dati e inizializza l'analizzatore
|
||||
self._load_data_and_setup(archive_filepath)
|
||||
@ -74,8 +75,9 @@ class AnalysisWindow(tk.Toplevel):
|
||||
self.latency_timestamps = []
|
||||
self.latency_values_ms = []
|
||||
|
||||
# Create a temporary hub and populate it with historical data
|
||||
self._hub = SimulationStateHub()
|
||||
# Create a temporary hub with unlimited history size for analysis
|
||||
# The default history_size of 200 is too small for full simulation playback
|
||||
self._hub = SimulationStateHub(history_size=100000)
|
||||
results = archive_data.get("simulation_results", {})
|
||||
for target_id_str, data in results.items():
|
||||
target_id = int(target_id_str)
|
||||
@ -231,7 +233,8 @@ class AnalysisWindow(tk.Toplevel):
|
||||
fig = Figure(figsize=(5, 6), dpi=100)
|
||||
|
||||
# Use GridSpec for aligned subplots with shared x-axis alignment
|
||||
gs = fig.add_gridspec(2, 1, height_ratios=[2, 1], hspace=0.3)
|
||||
# Increased top margin to avoid title overlap with toolbar
|
||||
gs = fig.add_gridspec(2, 1, height_ratios=[2, 1], hspace=0.35, top=0.95)
|
||||
|
||||
# Top subplot: Instantaneous Error
|
||||
self.ax = fig.add_subplot(gs[0, 0])
|
||||
@ -252,8 +255,8 @@ class AnalysisWindow(tk.Toplevel):
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
# Bottom subplot: Latency over time
|
||||
self.ax_latency = fig.add_subplot(gs[1, 0], sharex=None)
|
||||
# Bottom subplot: Latency over time - SHARE X-AXIS with top plot for synchronized zoom
|
||||
self.ax_latency = fig.add_subplot(gs[1, 0], sharex=self.ax)
|
||||
self.ax_latency.set_title("Latency Evolution")
|
||||
self.ax_latency.set_xlabel(
|
||||
"Time (s)"
|
||||
@ -272,9 +275,24 @@ class AnalysisWindow(tk.Toplevel):
|
||||
|
||||
fig.tight_layout()
|
||||
|
||||
self.canvas = FigureCanvasTkAgg(fig, master=parent)
|
||||
# Create frame for toolbar and canvas
|
||||
plot_container = ttk.Frame(parent)
|
||||
plot_container.pack(fill=tk.BOTH, expand=True)
|
||||
|
||||
# Add matplotlib navigation toolbar for zoom/pan functionality at the top
|
||||
from matplotlib.backends.backend_tkagg import NavigationToolbar2Tk
|
||||
|
||||
toolbar_frame = ttk.Frame(plot_container)
|
||||
toolbar_frame.pack(side=tk.TOP, fill=tk.X)
|
||||
|
||||
self.canvas = FigureCanvasTkAgg(fig, master=plot_container)
|
||||
|
||||
toolbar = NavigationToolbar2Tk(self.canvas, toolbar_frame)
|
||||
toolbar.update()
|
||||
|
||||
# Pack canvas after toolbar so it's below
|
||||
self.canvas.get_tk_widget().pack(side=tk.TOP, fill=tk.BOTH, expand=True)
|
||||
self.canvas.draw()
|
||||
self.canvas.get_tk_widget().pack(fill=tk.BOTH, expand=True)
|
||||
|
||||
def _update_target_selector(self):
|
||||
"""Refresh the target selector Combobox with IDs from the state hub.
|
||||
@ -305,24 +323,62 @@ class AnalysisWindow(tk.Toplevel):
|
||||
def _update_stats_table(self, results: Dict):
|
||||
"""Populate the stats Treeview with aggregated error metrics.
|
||||
|
||||
Uses filtered error data (excluding spikes) if available.
|
||||
|
||||
Args:
|
||||
results (Dict): A mapping containing 'x', 'y', 'z' metrics and
|
||||
numerical aggregates such as mean, std_dev and rmse.
|
||||
"""
|
||||
self.stats_tree.delete(*self.stats_tree.get_children())
|
||||
|
||||
# Add rows for each error axis (X, Y, Z)
|
||||
for axis in ["x", "y", "z"]:
|
||||
self.stats_tree.insert(
|
||||
"",
|
||||
"end",
|
||||
values=(
|
||||
f"Error {axis.upper()}",
|
||||
f"{results[axis]['mean']:.3f}",
|
||||
f"{results[axis]['std_dev']:.3f}",
|
||||
f"{results[axis]['rmse']:.3f}",
|
||||
),
|
||||
)
|
||||
# Use filtered errors if available, otherwise fall back to analyzer results
|
||||
if hasattr(self, '_filtered_errors') and self._filtered_errors:
|
||||
import math
|
||||
# Compute statistics from filtered data
|
||||
for axis in ["x", "y", "z"]:
|
||||
errors = self._filtered_errors[axis]
|
||||
if errors:
|
||||
n = len(errors)
|
||||
mean = sum(errors) / n
|
||||
variance = sum((x - mean) ** 2 for x in errors) / n
|
||||
std_dev = math.sqrt(variance)
|
||||
rmse = math.sqrt(sum(x**2 for x in errors) / n)
|
||||
|
||||
self.stats_tree.insert(
|
||||
"",
|
||||
"end",
|
||||
values=(
|
||||
f"Error {axis.upper()}",
|
||||
f"{mean:.3f}",
|
||||
f"{std_dev:.3f}",
|
||||
f"{rmse:.3f}",
|
||||
),
|
||||
)
|
||||
else:
|
||||
# No data after filtering
|
||||
self.stats_tree.insert(
|
||||
"",
|
||||
"end",
|
||||
values=(
|
||||
f"Error {axis.upper()}",
|
||||
"N/A",
|
||||
"N/A",
|
||||
"N/A",
|
||||
),
|
||||
)
|
||||
else:
|
||||
# Fallback to analyzer results (unfiltered)
|
||||
for axis in ["x", "y", "z"]:
|
||||
self.stats_tree.insert(
|
||||
"",
|
||||
"end",
|
||||
values=(
|
||||
f"Error {axis.upper()}",
|
||||
f"{results[axis]['mean']:.3f}",
|
||||
f"{results[axis]['std_dev']:.3f}",
|
||||
f"{results[axis]['rmse']:.3f}",
|
||||
),
|
||||
)
|
||||
|
||||
# Add latency row if available
|
||||
if self.estimated_latency_ms is not None:
|
||||
@ -365,6 +421,8 @@ class AnalysisWindow(tk.Toplevel):
|
||||
def _update_plot(self, target_id: int):
|
||||
"""Update the matplotlib plot for the given target using hub history.
|
||||
|
||||
Also computes and stores filtered errors (excluding outliers) for statistics.
|
||||
|
||||
Args:
|
||||
target_id (int): The identifier of the target to plot errors for.
|
||||
"""
|
||||
@ -376,6 +434,8 @@ class AnalysisWindow(tk.Toplevel):
|
||||
self.ax.relim()
|
||||
self.ax.autoscale_view()
|
||||
self.canvas.draw_idle()
|
||||
# Clear filtered data cache
|
||||
self._filtered_errors = None
|
||||
return
|
||||
|
||||
times, errors_x, errors_y, errors_z = [], [], [], []
|
||||
@ -394,9 +454,88 @@ class AnalysisWindow(tk.Toplevel):
|
||||
errors_y.append(real_y - interp_y)
|
||||
errors_z.append(real_z - interp_z)
|
||||
|
||||
self.line_x.set_data(times, errors_x)
|
||||
self.line_y.set_data(times, errors_y)
|
||||
self.line_z.set_data(times, errors_z)
|
||||
if not times:
|
||||
self.line_x.set_data([], [])
|
||||
self.line_y.set_data([], [])
|
||||
self.line_z.set_data([], [])
|
||||
self.ax.relim()
|
||||
self.ax.autoscale_view()
|
||||
self.canvas.draw_idle()
|
||||
self._filtered_errors = None
|
||||
return
|
||||
|
||||
# Filter initial transient/acquisition spikes
|
||||
# Use a threshold based on the median of stable data (after initial period)
|
||||
filtered_times, filtered_x, filtered_y, filtered_z = [], [], [], []
|
||||
outlier_times, outlier_x, outlier_y, outlier_z = [], [], [], []
|
||||
|
||||
# Take a sample window after initial seconds to compute typical error magnitude
|
||||
min_time = min(times)
|
||||
sample_window_start = min_time + 5.0 # Skip first 5 seconds
|
||||
sample_window_end = min_time + 15.0 # Sample from 5s to 15s
|
||||
|
||||
sample_errors = []
|
||||
for i, t in enumerate(times):
|
||||
if sample_window_start <= t <= sample_window_end:
|
||||
err_magnitude = (errors_x[i]**2 + errors_y[i]**2 + errors_z[i]**2) ** 0.5
|
||||
sample_errors.append(err_magnitude)
|
||||
|
||||
# Compute threshold: median of sample + 10x (very permissive for normal errors)
|
||||
if sample_errors:
|
||||
import statistics
|
||||
median_err = statistics.median(sample_errors)
|
||||
# Use 20x median as threshold to catch only extreme outliers
|
||||
threshold = max(median_err * 20, 500.0) # At least 500 ft threshold
|
||||
else:
|
||||
# Fallback if no sample data available
|
||||
threshold = 1000.0
|
||||
|
||||
# Classify points as normal or outliers
|
||||
for i, t in enumerate(times):
|
||||
err_magnitude = (errors_x[i]**2 + errors_y[i]**2 + errors_z[i]**2) ** 0.5
|
||||
if err_magnitude > threshold:
|
||||
outlier_times.append(t)
|
||||
outlier_x.append(errors_x[i])
|
||||
outlier_y.append(errors_y[i])
|
||||
outlier_z.append(errors_z[i])
|
||||
else:
|
||||
filtered_times.append(t)
|
||||
filtered_x.append(errors_x[i])
|
||||
filtered_y.append(errors_y[i])
|
||||
filtered_z.append(errors_z[i])
|
||||
|
||||
# Store filtered errors for statistics computation
|
||||
self._filtered_errors = {
|
||||
'x': filtered_x,
|
||||
'y': filtered_y,
|
||||
'z': filtered_z
|
||||
}
|
||||
|
||||
# Plot filtered (normal) data
|
||||
self.line_x.set_data(filtered_times, filtered_x)
|
||||
self.line_y.set_data(filtered_times, filtered_y)
|
||||
self.line_z.set_data(filtered_times, filtered_z)
|
||||
|
||||
# Add annotation if outliers were detected
|
||||
# Clear previous annotations
|
||||
for txt in getattr(self.ax, '_spike_annotations', []):
|
||||
txt.remove()
|
||||
self.ax._spike_annotations = []
|
||||
|
||||
if outlier_times:
|
||||
# Add text annotation about filtered spikes
|
||||
outlier_count = len(outlier_times)
|
||||
max_outlier_mag = max((outlier_x[i]**2 + outlier_y[i]**2 + outlier_z[i]**2)**0.5
|
||||
for i in range(len(outlier_times)))
|
||||
annotation_text = (f"⚠ {outlier_count} acquisition spike(s) filtered\n"
|
||||
f"(max error: {max_outlier_mag:.0f} ft at t={outlier_times[0]:.1f}s)\n"
|
||||
f"Spikes excluded from statistics")
|
||||
txt = self.ax.text(0.02, 0.98, annotation_text,
|
||||
transform=self.ax.transAxes,
|
||||
verticalalignment='top',
|
||||
bbox=dict(boxstyle='round', facecolor='yellow', alpha=0.7),
|
||||
fontsize=9)
|
||||
self.ax._spike_annotations = [txt]
|
||||
|
||||
self.ax.relim()
|
||||
self.ax.autoscale_view()
|
||||
|
||||
Loading…
Reference in New Issue
Block a user