Skip to content

Commit 8d623c0

Browse files
committed
[Try] data_selection_window: Add coherence plot for data selection
1 parent c2011e0 commit 8d623c0

File tree

1 file changed

+83
-4
lines changed

1 file changed

+83
-4
lines changed

autotune/data_selection_window.py

Lines changed: 83 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
from matplotlib.widgets import SpanSelector
66

77
import numpy as np
8+
from scipy import signal
89

910
from data_extractor import DataExtractor
1011
from searchable_combo_box import SearchableComboBox
@@ -56,9 +57,9 @@ def __init__(self, filename):
5657

5758
self.input_ref = None
5859
self.output_ref = None
59-
self.figure = plt.figure(1)
60+
self.figure = plt.figure(figsize=(8, 6))
6061
self.canvas = FigureCanvas(self.figure)
61-
self.initPlot()
62+
self.initPlots()
6263

6364
layout_v = QVBoxLayout()
6465

@@ -183,10 +184,12 @@ def selectYData(self, index):
183184
def getTrimAirspeed(self):
184185
return self.data_extractor.getTrimAirspeed()
185186

186-
def initPlot(self):
187+
def initPlots(self):
187188
if self.input_ref is None:
188189
self.figure.clear()
189-
self.ax = self.figure.add_subplot(1,1,1)
190+
191+
# --- Time series Axes (Top) ---
192+
self.ax = self.figure.add_subplot(2,1,1)
190193
color_in = 'tab:blue'
191194
plot_refs = self.ax.plot([], [], color=color_in)
192195
self.input_ref = plot_refs[0]
@@ -207,6 +210,15 @@ def initPlot(self):
207210

208211
self.span = SpanSelector(self.ax_out, self.onselect, 'horizontal', useblit=False,
209212
props=dict(alpha=0.2, facecolor='green'), interactive=True)
213+
214+
# --- Coherence Plot (Bottom) ---
215+
self.ax_coherence = self.figure.add_subplot(2, 1, 2)
216+
self.ax_coherence.set_title("Coherence")
217+
self.ax_coherence.set_xlabel("Frequency [Hz]")
218+
self.ax_coherence.set_ylabel("Coherence")
219+
self.coherence_ref, = self.ax_coherence.plot([], [])
220+
221+
self.figure.tight_layout()
210222

211223
self.canvas.mpl_connect('scroll_event', self.zoom_fun)
212224
self.canvas.draw()
@@ -231,6 +243,71 @@ def plotY(self):
231243
self.ax_out.set_ylim([min_y, max_y])
232244
self.canvas.draw()
233245

246+
def plotCoherence(self):
247+
if len(self.t) == 0 or len(self.u) == 0 or len(self.y) == 0:
248+
return
249+
250+
# Use selected range if available
251+
if self.t_start is not None and self.t_stop is not None and self.t_stop > self.t_start:
252+
# Get indices within selected range
253+
ind_start = np.searchsorted(self.t, self.t_start)
254+
ind_stop = np.searchsorted(self.t, self.t_stop)
255+
256+
t_sel = self.t[ind_start:ind_stop]
257+
u_sel = self.u[ind_start:ind_stop]
258+
y_sel = self.y[ind_start:ind_stop]
259+
else:
260+
# Fall back to full signal if no range selected
261+
t_sel = self.t
262+
u_sel = self.u
263+
y_sel = self.y
264+
265+
num_samples = len(t_sel)
266+
if num_samples < 64: # I kind of made up this number -> maybe requires some research
267+
self.ax_coherence.clear()
268+
self.ax_coherence.set_title("Coherence (Selection too short)")
269+
self.ax_coherence.text(0.5, 0.5, f"Not enough data ({num_samples} samples).\nSelect a larger window.",
270+
ha='center', va='center', transform=self.ax_coherence.transAxes,
271+
fontsize=10, color='red')
272+
self.canvas.draw()
273+
return
274+
275+
# Estimate sampling frequency
276+
time_diffs = np.diff(t_sel)
277+
avg_time_diff = np.mean(time_diffs)
278+
if avg_time_diff == 0:
279+
return
280+
fs = 1 / avg_time_diff
281+
282+
# Choose segment size
283+
nperseg = min(256, num_samples // 4) # Also needs to be verified
284+
285+
# Compute coherence
286+
freq, Cuy = signal.coherence(u_sel, y_sel, fs, nperseg=nperseg)
287+
288+
# Update coherence plot
289+
self.ax_coherence.clear()
290+
self.ax_coherence.plot(freq, Cuy, label='Coherence(u, y)')
291+
self.ax_coherence.set_title("Coherence")
292+
self.ax_coherence.set_xlabel("Frequency [Hz]")
293+
self.ax_coherence.set_ylabel("Coherence")
294+
self.ax_coherence.set_xlim([min(freq), max(freq)])
295+
self.ax_coherence.set_ylim([0, 1])
296+
self.ax_coherence.grid(True)
297+
298+
# Add informative annotation
299+
duration = t_sel[-1] - t_sel[0]
300+
freq_res = fs / nperseg
301+
info_text = (f"Samples: {num_samples}, Duration: {duration:.2f}s, "
302+
f"fs: {fs:.1f}Hz, nperseg: {nperseg}, Δf: {freq_res:.2f}Hz")
303+
304+
self.ax_coherence.text(0.98, 0.02, info_text,
305+
ha='right', va='bottom',
306+
transform=self.ax_coherence.transAxes,
307+
fontsize=8, color='gray')
308+
309+
self.canvas.draw()
310+
234311
def onselect(self, xmin, xmax):
235312
indmin, indmax = np.searchsorted(self.t, (xmin, xmax))
236313
indmax = min(len(self.t) - 1, indmax)
@@ -241,6 +318,8 @@ def onselect(self, xmin, xmax):
241318
self.ax.set_xlim(self.t_start - 1.0, self.t_stop + 1.0)
242319
self.canvas.draw()
243320

321+
self.plotCoherence()
322+
244323
def zoom_fun(self, event):
245324
base_scale = 1.1
246325
# get the current x and y limits

0 commit comments

Comments
 (0)