Compare commits

...

2 Commits

Author SHA1 Message Date
6048ed9066 Python: calibration tool — ajout courbe moyenne glissante + légende
Affiche la moyenne glissante (même fenêtre 60ms que le XIAO) en plus
de la courbe brute sur chaque graphe, avec légende. Permet de comprendre
visuellement pourquoi un tir est déclenché même si le pic brut semble
ne pas atteindre le seuil.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-19 09:39:18 +01:00
ad72c2aad5 no message 2026-02-19 09:33:13 +01:00
3 changed files with 49 additions and 12 deletions

2
.gitignore vendored
View File

@ -3,3 +3,5 @@ Unreal/Intermediate/
Unreal/Plugins/WinBluetoothLE/Intermediate/
Unreal/Binaries/
Unreal/Plugins/WinBluetoothLE/Binaries/
Unreal/Plugins/PS_Win_BLE/Binaries/
Unreal/Plugins/PS_Win_BLE/Intermediate/

View File

@ -44,6 +44,9 @@ debug_mode = 3 # 0=OFF, 1=RAW, 2=TRIGGERS, 3=FULL (actif par défaut)
import numpy as np
X = np.arange(WINDOW_SIZE) # axe X fixe, ne change jamais
# Fenêtre de moyenne glissante en samples (60ms window / 50ms debug rate = ~1.2 → 3 samples min)
AVG_WINDOW = 3 # correspond à la accelWindow/gyroWindow/audioWindow envoyée au XIAO (60ms @ 20Hz)
# ─── BLE ────────────────────────────────────────────────
def debug_callback(sender, data):
global audio_max_global, shot_pending
@ -192,9 +195,12 @@ from matplotlib.animation import FuncAnimation
BG = '#1a1a2e'
PANEL = '#16213e'
TEXT = '#e0e0e0'
CA = '#4fc3f7' # accel
CG = '#81c784' # gyro
CM = '#ce93d8' # audio
CA = '#4fc3f7' # accel brut
CG = '#81c784' # gyro brut
CM = '#ce93d8' # audio brut
CA2 = '#0288d1' # accel moyenne (plus foncé)
CG2 = '#388e3c' # gyro moyenne
CM2 = '#7b1fa2' # audio moyenne
CT = '#ef5350' # trigger / seuil
CS = '#ff9800' # shot (orange vif)
GRID = '#2a2a4a'
@ -222,15 +228,23 @@ axes[3].set_ylim(-0.1, 1.5)
axes[3].set_yticks([]) # pas de graduations Y sur la timeline
# Créer les artistes UNE SEULE FOIS — on ne les recrée jamais
line_a, = axes[0].plot(X, list(accel_buf), color=CA, lw=1.5)
line_g, = axes[1].plot(X, list(gyro_buf), color=CG, lw=1.5)
line_m, = axes[2].plot(X, list(audio_buf), color=CM, lw=1.5)
line_s, = axes[3].plot(X, list(shot_buf), color=CS, lw=0, marker='|',
line_a, = axes[0].plot(X, list(accel_buf), color=CA, lw=1.0, alpha=0.5, label='Brut')
line_g, = axes[1].plot(X, list(gyro_buf), color=CG, lw=1.0, alpha=0.5, label='Brut')
line_m, = axes[2].plot(X, list(audio_buf), color=CM, lw=1.0, alpha=0.5, label='Brut')
line_a2, = axes[0].plot(X, list(accel_buf), color=CA2, lw=2.0, label=f'Moyenne ({AVG_WINDOW} pts / 60ms)')
line_g2, = axes[1].plot(X, list(gyro_buf), color=CG2, lw=2.0, label=f'Moyenne ({AVG_WINDOW} pts / 60ms)')
line_m2, = axes[2].plot(X, list(audio_buf), color=CM2, lw=2.0, label=f'Moyenne ({AVG_WINDOW} pts / 60ms)')
line_s, = axes[3].plot(X, list(shot_buf), color=CS, lw=0, marker='|',
markersize=20, markeredgewidth=2.5) # barres verticales
thr_a = axes[0].axhline(thresholds["accel"], color=CT, ls='--', lw=1.5)
thr_g = axes[1].axhline(thresholds["gyro"], color=CT, ls='--', lw=1.5)
thr_m = axes[2].axhline(thresholds["audio"], color=CT, ls='--', lw=1.5)
thr_a = axes[0].axhline(thresholds["accel"], color=CT, ls='--', lw=1.5, label='Seuil')
thr_g = axes[1].axhline(thresholds["gyro"], color=CT, ls='--', lw=1.5, label='Seuil')
thr_m = axes[2].axhline(thresholds["audio"], color=CT, ls='--', lw=1.5, label='Seuil')
# Légendes
for ax in axes[:3]:
ax.legend(loc='upper left', fontsize=7, facecolor=PANEL, edgecolor='#444466',
labelcolor=TEXT, framealpha=0.8)
from matplotlib.patches import Rectangle
from matplotlib.collections import PatchCollection
@ -262,17 +276,30 @@ def update_spans(spans, trig_list):
for i, span in enumerate(spans):
span.set_alpha(0.25 if i < len(tl) and tl[i] else 0)
def rolling_avg(arr, window):
"""Moyenne glissante simple — même logique que le XIAO"""
kernel = np.ones(window) / window
return np.convolve(arr, kernel, mode='same')
def update(frame):
# Snapshot des buffers (rapide)
a = np.array(accel_buf)
g = np.array(gyro_buf)
m = np.array(audio_buf)
m = np.array(audio_buf, dtype=float)
s = np.array(shot_buf)
# Moyennes glissantes (même fenêtre que le XIAO)
a_avg = rolling_avg(a, AVG_WINDOW)
g_avg = rolling_avg(g, AVG_WINDOW)
m_avg = rolling_avg(m, AVG_WINDOW)
# Mise à jour des données des lignes
line_a.set_ydata(a)
line_g.set_ydata(g)
line_m.set_ydata(m)
line_a2.set_ydata(a_avg)
line_g2.set_ydata(g_avg)
line_m2.set_ydata(m_avg)
line_s.set_ydata(s)
# Mise à jour des seuils
@ -319,7 +346,7 @@ def update(frame):
f"Debug XIAO : {DEBUG_MODE_NAMES[debug_mode]} [NUM0 = cycle OFF->RAW->TRIGGERS->FULL] | "
f"MinSensors : {min_sensors}/3 [NUM6=+1 NUM4=-1]")
return (line_a, line_g, line_m, line_s,
return (line_a, line_g, line_m, line_a2, line_g2, line_m2, line_s,
thr_a, thr_g, thr_m,
*titles, status_txt, debug_txt)

View File

@ -0,0 +1,8 @@
[FilterPlugin]
; This section lists additional files which will be packaged along with your plugin. Paths should be listed relative to the root plugin directory, and
; may include "...", "*", and "?" wildcards to match directories, files, and individual characters respectively.
;
; Examples:
; /README.txt
; /Extras/...
; /Binaries/ThirdParty/*.dll