Spaces:
Running
Running
Hugo.V commited on
Commit ·
ec7cda2
1
Parent(s): 4492f35
commentaires
Browse files- jericho/main.py +123 -124
jericho/main.py
CHANGED
|
@@ -38,175 +38,174 @@ def moving_average(values: list[float], window: int = 7) -> np.ndarray: # fct li
|
|
| 38 |
return smoothed[: len(arr)]
|
| 39 |
|
| 40 |
class SignalMapper: # classe map signaux visage vers robot
|
| 41 |
-
#
|
| 42 |
-
HEAD_YAW_MAX = np.deg2rad(5)
|
| 43 |
-
HEAD_PITCH_MAX = np.deg2rad(15)
|
| 44 |
-
HEAD_ROLL_MAX = np.deg2rad(15)
|
| 45 |
-
HEAD_Y_MAX = 0.03
|
| 46 |
-
HEAD_Z_MAX = 0.03
|
| 47 |
-
BODY_YAW_MAX = np.deg2rad(15)
|
| 48 |
-
ANTENNA_MAX = np.deg2rad(120)
|
| 49 |
-
ANTENNA_MAX_alt = np.deg2rad(45)
|
| 50 |
-
ANTENNA_MAX_col = np.deg2rad(110)
|
| 51 |
-
COMBINED_YAW_LIMIT = np.deg2rad(60)
|
| 52 |
-
|
| 53 |
-
# multiplicateurs
|
| 54 |
-
YAW_AMP = 8.0
|
| 55 |
-
PITCH_AMP = 5.0
|
| 56 |
-
ROLL_AMP = 3.0
|
| 57 |
-
CX_TO_Y_SCALE = 0.12
|
| 58 |
-
CX_TO_BODY_YAW = 0.8
|
| 59 |
-
CY_TO_Z_SCALE = -0.10
|
| 60 |
|
| 61 |
# fenetres lissage
|
| 62 |
-
SMOOTH_WINDOW_HEAD = 7
|
| 63 |
|
| 64 |
-
def __init__(self, frames: list[dict]):
|
| 65 |
-
self.frames = frames
|
| 66 |
self._compute_baselines() # calcul moyennes ref
|
| 67 |
self.commands = self._build_commands() # genere coms robot
|
| 68 |
|
| 69 |
def _compute_baselines(self): # fct valeurs moyennes tte la video
|
| 70 |
-
def mean(key, calc_func=None):
|
| 71 |
-
if calc_func: return float(np.mean([calc_func(f) for f in self.frames]))
|
| 72 |
-
return float(np.mean([f[key] for f in self.frames]))
|
| 73 |
-
def std(key, calc_func=None):
|
| 74 |
-
if calc_func: return max(float(np.std([calc_func(f) for f in self.frames])), 1e-6)
|
| 75 |
-
return max(float(np.std([f[key] for f in self.frames])), 1e-6)
|
| 76 |
|
| 77 |
# calcul refs pour chaque signal (tete + position uniquement, emotions deja dans le JSON via emotion.py)
|
| 78 |
-
self.yaw_mean = mean("yaw")
|
| 79 |
-
self.pitch_mean = mean("pitch")
|
| 80 |
-
self.roll_mean = mean("roll")
|
| 81 |
-
self.cx_mean = mean("face_cx")
|
| 82 |
-
self.cy_mean = mean("face_cy")
|
| 83 |
|
| 84 |
def _colere(self, t_state): # bouge antennes mode colere
|
| 85 |
-
if t_state < 0.15:
|
| 86 |
-
angle = -1.0 * (t_state / 0.15) * self.ANTENNA_MAX_col
|
| 87 |
-
elif t_state < 0.40:
|
| 88 |
-
angle = (-1.0 + 1.0 * ((t_state - 0.15) / 0.15)) * self.ANTENNA_MAX_col
|
| 89 |
-
elif t_state < 0.65:
|
| 90 |
-
angle = -1.0 * ((t_state - 0.3) / 0.15) * self.ANTENNA_MAX_col
|
| 91 |
-
else:
|
| 92 |
-
angle = -1.0 * self.ANTENNA_MAX_col
|
| 93 |
-
return angle, -angle
|
| 94 |
|
| 95 |
def _tristesse(self, t_state): # bouge antennes mode tristesse
|
| 96 |
-
target_angle = -1.0 * (1.0 - math.exp(-1.5 * t_state)) * self.ANTENNA_MAX
|
| 97 |
-
return (target_angle - 0.2), -(target_angle - 0.2)
|
| 98 |
|
| 99 |
def _joie(self, t_state): # bouge antennes mode joie (vibre)
|
| 100 |
-
oscillation = 0.5 * math.sin(2 * math.pi * 4.0 * t_state)
|
| 101 |
-
angle = (0.5 + oscillation) * self.ANTENNA_MAX_alt
|
| 102 |
-
return -angle, angle
|
| 103 |
|
| 104 |
def _content(self, t_state): # bouge antennes mode content
|
| 105 |
-
oscillation = 0.8 * math.sin(2 * math.pi * 0.5 * t_state)
|
| 106 |
-
angle = oscillation * self.ANTENNA_MAX_alt
|
| 107 |
-
return angle, angle
|
| 108 |
|
| 109 |
def _surprise(self, t_state): # bouge antennes mode surprise
|
| 110 |
-
angle = 1.0 * (1.0 - math.exp(-14.0 * t_state)) * self.ANTENNA_MAX_alt
|
| 111 |
-
return -(angle - 0.8), (angle - 0.8)
|
| 112 |
|
| 113 |
def _neutre(self, t_state): # bouge antennes mode neutre
|
| 114 |
-
angle = 1.0 * (1.0 - math.exp(-14.0 * t_state)) * self.ANTENNA_MAX_alt
|
| 115 |
-
return -(angle - 0.3), (angle - 0.3)
|
| 116 |
|
| 117 |
def _peur(self, t_state): # bouge antennes mode peur (tremble)
|
| 118 |
-
tremblement = 0.1 * math.sin(2 * math.pi * 15.0 * t_state)
|
| 119 |
-
angle = -1.0 * (1.0 - math.exp(-1.5 * t_state) + tremblement) * self.ANTENNA_MAX
|
| 120 |
-
return (angle - 0.2), -(angle - 0.2)
|
| 121 |
|
| 122 |
def _compute_antennas(self, emotion, t_state): # fct select mvt selon emotion
|
| 123 |
-
if emotion == "TRISTESSE":
|
| 124 |
-
return self._tristesse(t_state)
|
| 125 |
-
elif emotion == "JOIE":
|
| 126 |
-
return self._joie(t_state)
|
| 127 |
-
elif emotion == "CONTENT":
|
| 128 |
-
return self._content(t_state)
|
| 129 |
-
elif emotion == "SURPRISE":
|
| 130 |
-
return self._surprise(t_state)
|
| 131 |
-
elif emotion == "NEUTRE":
|
| 132 |
-
return self._neutre(t_state)
|
| 133 |
-
elif emotion == "COLERE":
|
| 134 |
-
return self._colere(t_state)
|
| 135 |
-
elif emotion == "PEUR":
|
| 136 |
-
return self._peur(t_state)
|
| 137 |
-
else:
|
| 138 |
-
angle = -1.0 * self.ANTENNA_MAX_col
|
| 139 |
-
return angle, -angle
|
| 140 |
|
| 141 |
def _build_commands(self) -> list[dict]: # assemble signaux en tab commandes
|
| 142 |
-
n = len(self.frames)
|
| 143 |
-
raw_yaw = np.array([f["yaw"] - self.yaw_mean for f in self.frames])
|
| 144 |
-
raw_pitch = np.array([f["pitch"] - self.pitch_mean for f in self.frames])
|
| 145 |
-
raw_roll = np.array([f["roll"] - self.roll_mean for f in self.frames])
|
| 146 |
-
raw_cx = np.array([f["face_cx"] - self.cx_mean for f in self.frames])
|
| 147 |
-
raw_cy = np.array([f["face_cy"] - self.cy_mean for f in self.frames])
|
| 148 |
-
|
| 149 |
-
w_h = self.SMOOTH_WINDOW_HEAD
|
| 150 |
-
|
| 151 |
-
# applique fct lissage
|
| 152 |
-
s_yaw = moving_average(raw_yaw, w_h)
|
| 153 |
-
s_pitch = moving_average(raw_pitch, w_h)
|
| 154 |
-
s_roll = moving_average(raw_roll, w_h)
|
| 155 |
-
s_cx = moving_average(raw_cx, w_h)
|
| 156 |
-
s_cy = moving_average(raw_cy, w_h)
|
| 157 |
-
|
| 158 |
-
commands = []
|
| 159 |
-
current_emotion = "NEUTRE"
|
| 160 |
-
emotion_start_time = 0.0
|
| 161 |
|
| 162 |
for i in range(n): # boucle pr chaque image
|
| 163 |
-
t = self.frames[i]["timestamp_s"]
|
| 164 |
|
| 165 |
# mise a echelle + clamp pos tete
|
| 166 |
-
head_yaw = self._clamp(np.deg2rad(s_yaw[i] * self.YAW_AMP), self.HEAD_YAW_MAX)
|
| 167 |
-
head_pitch = self._clamp(np.deg2rad(s_pitch[i] * self.PITCH_AMP), self.HEAD_PITCH_MAX)
|
| 168 |
-
head_roll = self._clamp(np.deg2rad(s_roll[i] * self.ROLL_AMP), self.HEAD_ROLL_MAX)
|
| 169 |
-
head_y = self._clamp(s_cx[i] * self.CX_TO_Y_SCALE, self.HEAD_Y_MAX)
|
| 170 |
-
head_z = self._clamp(s_cy[i] * self.CY_TO_Z_SCALE, self.HEAD_Z_MAX)
|
| 171 |
-
body_yaw = self._clamp(s_cx[i] * self.CX_TO_BODY_YAW, self.BODY_YAW_MAX)
|
| 172 |
|
| 173 |
diff = body_yaw - head_yaw # verif coherence corps cou
|
| 174 |
-
if abs(diff) > self.COMBINED_YAW_LIMIT:
|
| 175 |
-
body_yaw = head_yaw + np.sign(diff) * self.COMBINED_YAW_LIMIT
|
| 176 |
|
| 177 |
# recupere emotion deja calculee par emotion.py (stockee dans signals.json)
|
| 178 |
-
detected_emotion = self.frames[i].get("emotion", "NEUTRE")
|
| 179 |
|
| 180 |
if detected_emotion != current_emotion: # top chrono si changmt emo
|
| 181 |
-
current_emotion = detected_emotion
|
| 182 |
-
emotion_start_time = t
|
| 183 |
|
| 184 |
-
t_state = t - emotion_start_time
|
| 185 |
-
ant_left, ant_right = self._compute_antennas(current_emotion, t_state) #
|
| 186 |
|
| 187 |
commands.append({ # ajoute com dico complet
|
| 188 |
-
"timestamp_s": round(t, 4),
|
| 189 |
-
"head": {
|
| 190 |
-
"x": 0.0,
|
| 191 |
-
"y": round(float(head_y), 5),
|
| 192 |
-
"z": round(float(head_z), 5),
|
| 193 |
-
"roll": round(float(head_roll), 5),
|
| 194 |
-
"pitch": round(float(head_pitch), 5),
|
| 195 |
-
"yaw": round(float(head_yaw), 5),
|
| 196 |
},
|
| 197 |
-
"body_yaw": round(float(body_yaw), 5),
|
| 198 |
-
"antennas": [
|
| 199 |
-
round(float(self._clamp(ant_left, self.ANTENNA_MAX)), 5),
|
| 200 |
-
round(float(self._clamp(ant_right, self.ANTENNA_MAX)), 5),
|
| 201 |
],
|
| 202 |
-
"emotion": current_emotion
|
| 203 |
})
|
| 204 |
-
return commands
|
| 205 |
|
| 206 |
-
@staticmethod
|
| 207 |
def _clamp(value: float, limit: float) -> float: # limite valeurs min max
|
| 208 |
-
return float(max(-limit, min(limit, value)))
|
| 209 |
-
|
| 210 |
|
| 211 |
class VideoPayload(BaseModel): # struct payload upload video web
|
| 212 |
name: str
|
|
|
|
| 38 |
return smoothed[: len(arr)]
|
| 39 |
|
| 40 |
class SignalMapper: # classe map signaux visage vers robot
|
| 41 |
+
# limites angles robot
|
| 42 |
+
HEAD_YAW_MAX = np.deg2rad(5) # max rota tete g/d
|
| 43 |
+
HEAD_PITCH_MAX = np.deg2rad(15) # max inclin tete ht/bas
|
| 44 |
+
HEAD_ROLL_MAX = np.deg2rad(15) # max penchement tete
|
| 45 |
+
HEAD_Y_MAX = 0.03 # max pos y tete
|
| 46 |
+
HEAD_Z_MAX = 0.03 # max pos z tete
|
| 47 |
+
BODY_YAW_MAX = np.deg2rad(15) # max rota corps g/d
|
| 48 |
+
ANTENNA_MAX = np.deg2rad(120) # max ampli antennes
|
| 49 |
+
ANTENNA_MAX_alt = np.deg2rad(45) # max ampli antennes alt
|
| 50 |
+
ANTENNA_MAX_col = np.deg2rad(110) # max ampli antennes colere
|
| 51 |
+
COMBINED_YAW_LIMIT = np.deg2rad(60) # max ecart tete corps
|
| 52 |
+
|
| 53 |
+
# multiplicateurs mouvements
|
| 54 |
+
YAW_AMP = 8.0 # coeff multi rota g/d
|
| 55 |
+
PITCH_AMP = 5.0 # coeff multi inclin ht/bas
|
| 56 |
+
ROLL_AMP = 3.0 # coeff multi penchement
|
| 57 |
+
CX_TO_Y_SCALE = 0.12 # scale cx vers pos y
|
| 58 |
+
CX_TO_BODY_YAW = 0.8 # scale cx vers rota corps
|
| 59 |
+
CY_TO_Z_SCALE = -0.10 # scale cy vers pos z
|
| 60 |
|
| 61 |
# fenetres lissage
|
| 62 |
+
SMOOTH_WINDOW_HEAD = 7 # val fenetre lissage tete
|
| 63 |
|
| 64 |
+
def __init__(self, frames: list[dict]): # init de la classe
|
| 65 |
+
self.frames = frames # recup frames video
|
| 66 |
self._compute_baselines() # calcul moyennes ref
|
| 67 |
self.commands = self._build_commands() # genere coms robot
|
| 68 |
|
| 69 |
def _compute_baselines(self): # fct valeurs moyennes tte la video
|
| 70 |
+
def mean(key, calc_func=None): # fct calc moyenne
|
| 71 |
+
if calc_func: return float(np.mean([calc_func(f) for f in self.frames])) # moy si fct calc donnee
|
| 72 |
+
return float(np.mean([f[key] for f in self.frames])) # moy simple sur cle
|
| 73 |
+
def std(key, calc_func=None): # fct calc ecart type
|
| 74 |
+
if calc_func: return max(float(np.std([calc_func(f) for f in self.frames])), 1e-6) # ecart type si fct
|
| 75 |
+
return max(float(np.std([f[key] for f in self.frames])), 1e-6) # ecart type simple
|
| 76 |
|
| 77 |
# calcul refs pour chaque signal (tete + position uniquement, emotions deja dans le JSON via emotion.py)
|
| 78 |
+
self.yaw_mean = mean("yaw") # moy ref rota g/d
|
| 79 |
+
self.pitch_mean = mean("pitch") # moy ref inclin ht/bas
|
| 80 |
+
self.roll_mean = mean("roll") # moy ref penchement
|
| 81 |
+
self.cx_mean = mean("face_cx") # moy ref centre x
|
| 82 |
+
self.cy_mean = mean("face_cy") # moy ref centre y
|
| 83 |
|
| 84 |
def _colere(self, t_state): # bouge antennes mode colere
|
| 85 |
+
if t_state < 0.15: # phase 1 debut
|
| 86 |
+
angle = -1.0 * (t_state / 0.15) * self.ANTENNA_MAX_col # angle tombe vite
|
| 87 |
+
elif t_state < 0.40: # phase 2
|
| 88 |
+
angle = (-1.0 + 1.0 * ((t_state - 0.15) / 0.15)) * self.ANTENNA_MAX_col # angle remonte
|
| 89 |
+
elif t_state < 0.65: # phase 3
|
| 90 |
+
angle = -1.0 * ((t_state - 0.3) / 0.15) * self.ANTENNA_MAX_col # angle retombe
|
| 91 |
+
else: # phase 4 fin
|
| 92 |
+
angle = -1.0 * self.ANTENNA_MAX_col # maintient angle bas
|
| 93 |
+
return angle, -angle # renvoi angle g et d inverses
|
| 94 |
|
| 95 |
def _tristesse(self, t_state): # bouge antennes mode tristesse
|
| 96 |
+
target_angle = -1.0 * (1.0 - math.exp(-1.5 * t_state)) * self.ANTENNA_MAX # calc descente douce
|
| 97 |
+
return (target_angle - 0.2), -(target_angle - 0.2) # renvoi angle g et d avec decallage
|
| 98 |
|
| 99 |
def _joie(self, t_state): # bouge antennes mode joie (vibre)
|
| 100 |
+
oscillation = 0.5 * math.sin(2 * math.pi * 4.0 * t_state) # calc onde rapide
|
| 101 |
+
angle = (0.5 + oscillation) * self.ANTENNA_MAX_alt # mix onde et ampli
|
| 102 |
+
return -angle, angle # renvoi angle g et d
|
| 103 |
|
| 104 |
def _content(self, t_state): # bouge antennes mode content
|
| 105 |
+
oscillation = 0.8 * math.sin(2 * math.pi * 0.5 * t_state) # calc onde lente
|
| 106 |
+
angle = oscillation * self.ANTENNA_MAX_alt # mix onde et ampli
|
| 107 |
+
return angle, angle # renvoi angle g et d mm sens
|
| 108 |
|
| 109 |
def _surprise(self, t_state): # bouge antennes mode surprise
|
| 110 |
+
angle = 1.0 * (1.0 - math.exp(-14.0 * t_state)) * self.ANTENNA_MAX_alt # calc montee tres rapide
|
| 111 |
+
return -(angle - 0.8), (angle - 0.8) # renvoi angle g et d decalles
|
| 112 |
|
| 113 |
def _neutre(self, t_state): # bouge antennes mode neutre
|
| 114 |
+
angle = 1.0 * (1.0 - math.exp(-14.0 * t_state)) * self.ANTENNA_MAX_alt # calc montee rapide
|
| 115 |
+
return -(angle - 0.3), (angle - 0.3) # renvoi angle g et d decalles
|
| 116 |
|
| 117 |
def _peur(self, t_state): # bouge antennes mode peur (tremble)
|
| 118 |
+
tremblement = 0.1 * math.sin(2 * math.pi * 15.0 * t_state) # calc micro ondes
|
| 119 |
+
angle = -1.0 * (1.0 - math.exp(-1.5 * t_state) + tremblement) * self.ANTENNA_MAX # mix baisse et tremblement
|
| 120 |
+
return (angle - 0.2), -(angle - 0.2) # renvoi angle g et d decalles
|
| 121 |
|
| 122 |
def _compute_antennas(self, emotion, t_state): # fct select mvt selon emotion
|
| 123 |
+
if emotion == "TRISTESSE": # si emo est tristesse
|
| 124 |
+
return self._tristesse(t_state) # exe fct tristesse
|
| 125 |
+
elif emotion == "JOIE": # si emo est joie
|
| 126 |
+
return self._joie(t_state) # exe fct joie
|
| 127 |
+
elif emotion == "CONTENT": # si emo est content
|
| 128 |
+
return self._content(t_state) # exe fct content
|
| 129 |
+
elif emotion == "SURPRISE": # si emo est surprise
|
| 130 |
+
return self._surprise(t_state) # exe fct surprise
|
| 131 |
+
elif emotion == "NEUTRE": # si emo est neutre
|
| 132 |
+
return self._neutre(t_state) # exe fct neutre
|
| 133 |
+
elif emotion == "COLERE": # si emo est colere
|
| 134 |
+
return self._colere(t_state) # exe fct colere
|
| 135 |
+
elif emotion == "PEUR": # si emo est peur
|
| 136 |
+
return self._peur(t_state) # exe fct peur
|
| 137 |
+
else: # si emo pas co
|
| 138 |
+
angle = -1.0 * self.ANTENNA_MAX_col # angle defaut
|
| 139 |
+
return angle, -angle # renvoi angle defaut
|
| 140 |
|
| 141 |
def _build_commands(self) -> list[dict]: # assemble signaux en tab commandes
|
| 142 |
+
n = len(self.frames) # nb img total
|
| 143 |
+
raw_yaw = np.array([f["yaw"] - self.yaw_mean for f in self.frames]) # recup et deduit moy yaw
|
| 144 |
+
raw_pitch = np.array([f["pitch"] - self.pitch_mean for f in self.frames]) # recup et deduit moy pitch
|
| 145 |
+
raw_roll = np.array([f["roll"] - self.roll_mean for f in self.frames]) # recup et deduit moy roll
|
| 146 |
+
raw_cx = np.array([f["face_cx"] - self.cx_mean for f in self.frames]) # recup et deduit moy cx
|
| 147 |
+
raw_cy = np.array([f["face_cy"] - self.cy_mean for f in self.frames]) # recup et deduit moy cy
|
| 148 |
+
|
| 149 |
+
w_h = self.SMOOTH_WINDOW_HEAD # recup val lissage
|
| 150 |
+
|
| 151 |
+
# applique fct lissage tete + position
|
| 152 |
+
s_yaw = moving_average(raw_yaw, w_h) # lisse yaw
|
| 153 |
+
s_pitch = moving_average(raw_pitch, w_h) # lisse pitch
|
| 154 |
+
s_roll = moving_average(raw_roll, w_h) # lisse roll
|
| 155 |
+
s_cx = moving_average(raw_cx, w_h) # lisse cx
|
| 156 |
+
s_cy = moving_average(raw_cy, w_h) # lisse cy
|
| 157 |
+
|
| 158 |
+
commands = [] # init tab coms vides
|
| 159 |
+
current_emotion = "NEUTRE" # init emo de depart
|
| 160 |
+
emotion_start_time = 0.0 # init tps de depart
|
| 161 |
|
| 162 |
for i in range(n): # boucle pr chaque image
|
| 163 |
+
t = self.frames[i]["timestamp_s"] # recup tps img i
|
| 164 |
|
| 165 |
# mise a echelle + clamp pos tete
|
| 166 |
+
head_yaw = self._clamp(np.deg2rad(s_yaw[i] * self.YAW_AMP), self.HEAD_YAW_MAX) # calc max yaw tete
|
| 167 |
+
head_pitch = self._clamp(np.deg2rad(s_pitch[i] * self.PITCH_AMP), self.HEAD_PITCH_MAX) # calc max pitch
|
| 168 |
+
head_roll = self._clamp(np.deg2rad(s_roll[i] * self.ROLL_AMP), self.HEAD_ROLL_MAX) # calc max roll
|
| 169 |
+
head_y = self._clamp(s_cx[i] * self.CX_TO_Y_SCALE, self.HEAD_Y_MAX) # calc max pos y
|
| 170 |
+
head_z = self._clamp(s_cy[i] * self.CY_TO_Z_SCALE, self.HEAD_Z_MAX) # calc max pos z
|
| 171 |
+
body_yaw = self._clamp(s_cx[i] * self.CX_TO_BODY_YAW, self.BODY_YAW_MAX) # calc max yaw corps
|
| 172 |
|
| 173 |
diff = body_yaw - head_yaw # verif coherence corps cou
|
| 174 |
+
if abs(diff) > self.COMBINED_YAW_LIMIT: # si ecart trop grand
|
| 175 |
+
body_yaw = head_yaw + np.sign(diff) * self.COMBINED_YAW_LIMIT # corrige ecart max
|
| 176 |
|
| 177 |
# recupere emotion deja calculee par emotion.py (stockee dans signals.json)
|
| 178 |
+
detected_emotion = self.frames[i].get("emotion", "NEUTRE") # recup emo sinon neutre
|
| 179 |
|
| 180 |
if detected_emotion != current_emotion: # top chrono si changmt emo
|
| 181 |
+
current_emotion = detected_emotion # maj nv emo
|
| 182 |
+
emotion_start_time = t # maj tps depart emo
|
| 183 |
|
| 184 |
+
t_state = t - emotion_start_time # calc duree emo
|
| 185 |
+
ant_left, ant_right = self._compute_antennas(current_emotion, t_state) # calc mvt antennes
|
| 186 |
|
| 187 |
commands.append({ # ajoute com dico complet
|
| 188 |
+
"timestamp_s": round(t, 4), # tps au format court
|
| 189 |
+
"head": { # dico tete
|
| 190 |
+
"x": 0.0, # axe x
|
| 191 |
+
"y": round(float(head_y), 5), # val y
|
| 192 |
+
"z": round(float(head_z), 5), # val z
|
| 193 |
+
"roll": round(float(head_roll), 5), # val roll
|
| 194 |
+
"pitch": round(float(head_pitch), 5), # val pitch
|
| 195 |
+
"yaw": round(float(head_yaw), 5), # val yaw
|
| 196 |
},
|
| 197 |
+
"body_yaw": round(float(body_yaw), 5), # val yaw corps
|
| 198 |
+
"antennas": [ # tab antennes
|
| 199 |
+
round(float(self._clamp(ant_left, self.ANTENNA_MAX)), 5), # verif et garde angle g
|
| 200 |
+
round(float(self._clamp(ant_right, self.ANTENNA_MAX)), 5), # verif et garde angle d
|
| 201 |
],
|
| 202 |
+
"emotion": current_emotion # recup nom emo
|
| 203 |
})
|
| 204 |
+
return commands # renvoi toutes les coms pr rob
|
| 205 |
|
| 206 |
+
@staticmethod # decorateur statique sans self
|
| 207 |
def _clamp(value: float, limit: float) -> float: # limite valeurs min max
|
| 208 |
+
return float(max(-limit, min(limit, value))) # recadre val entre lim max et min
|
|
|
|
| 209 |
|
| 210 |
class VideoPayload(BaseModel): # struct payload upload video web
|
| 211 |
name: str
|