3v324v23 commited on
Commit
01c99e7
·
1 Parent(s): d16b827

Création des boutons

Browse files
jericho/main.py CHANGED
@@ -245,6 +245,8 @@ class Jericho(ReachyMiniApp):
245
  self.antennas_enabled = True
246
  self.sound_play_requested = False
247
  self.play_robot_requested = False
 
 
248
  self.current_commands = []
249
  self.current_timestamps = []
250
  self.current_project_name = None
@@ -291,6 +293,9 @@ class Jericho(ReachyMiniApp):
291
  return len(commands)
292
 
293
  def _set_playback_state(self, project_name: str | None, current_time: float, playing: bool, *, dirty: bool = True, stop_requested: bool = False):
 
 
 
294
  with self.playback_lock:
295
  self.playback_state.update({
296
  "project_name": project_name,
@@ -415,6 +420,16 @@ class Jericho(ReachyMiniApp):
415
  raise HTTPException(status_code=500, detail=f"Erreur de suppression: {str(e)}")
416
  raise HTTPException(status_code=404, detail="Projet introuvable.")
417
 
 
 
 
 
 
 
 
 
 
 
418
  @self.settings_app.post("/api/playback")
419
  def sync_playback(payload: PlaybackPayload):
420
 
@@ -466,10 +481,109 @@ class Jericho(ReachyMiniApp):
466
  except Exception as e:
467
  raise HTTPException(status_code=500, detail=str(e))
468
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
469
  def run(self, reachy_mini: ReachyMini, stop_event: threading.Event):
470
  reachy_mini.goto_target(create_head_pose(), antennas=[0.0, 0.0], duration=1.0)
471
  time.sleep(1.2)
472
 
 
 
 
 
 
 
 
473
  while not stop_event.is_set():
474
  with self.playback_lock:
475
  commands_to_play = list(self.current_commands)
 
245
  self.antennas_enabled = True
246
  self.sound_play_requested = False
247
  self.play_robot_requested = False
248
+ self.manual_emotion = None # émotion choisie via bouton
249
+ self.manual_emotion_lock = threading.Lock()
250
  self.current_commands = []
251
  self.current_timestamps = []
252
  self.current_project_name = None
 
293
  return len(commands)
294
 
295
  def _set_playback_state(self, project_name: str | None, current_time: float, playing: bool, *, dirty: bool = True, stop_requested: bool = False):
296
+ if playing:
297
+ with self.manual_emotion_lock:
298
+ self.manual_emotion = None
299
  with self.playback_lock:
300
  self.playback_state.update({
301
  "project_name": project_name,
 
420
  raise HTTPException(status_code=500, detail=f"Erreur de suppression: {str(e)}")
421
  raise HTTPException(status_code=404, detail="Projet introuvable.")
422
 
423
+ class EmotionPayload(BaseModel):
424
+ emotion: str | None = None # None ou "" = annuler
425
+
426
+ @self.settings_app.post("/api/emotion")
427
+ def set_emotion(payload: EmotionPayload):
428
+ emotion = (payload.emotion or "").strip().upper() or None
429
+ with self.manual_emotion_lock:
430
+ self.manual_emotion = emotion
431
+ return {"status": "ok", "emotion": emotion}
432
+
433
  @self.settings_app.post("/api/playback")
434
  def sync_playback(payload: PlaybackPayload):
435
 
 
481
  except Exception as e:
482
  raise HTTPException(status_code=500, detail=str(e))
483
 
484
+ def _run_manual_emotion_loop(self, reachy_mini: ReachyMini, stop_event: threading.Event):
485
+ """Thread secondaire : anime les antennes selon l'émotion choisie via bouton."""
486
+ ANTENNA_MAX = np.deg2rad(120)
487
+ ANTENNA_MAX_alt = np.deg2rad(45)
488
+ ANTENNA_MAX_col = np.deg2rad(110)
489
+ COLERE_PERIOD = 5.0 # répétition colère toutes les 5 s
490
+ COLERE_ANIM = 1.0 # durée de l'animation colère (s)
491
+
492
+ current = None
493
+ t_start = time.time()
494
+ colere_last = 0.0
495
+
496
+ while not stop_event.is_set():
497
+ with self.manual_emotion_lock:
498
+ emotion = self.manual_emotion
499
+ with self.playback_lock:
500
+ video_playing = bool(self.playback_state.get("playing", False))
501
+
502
+ # Si une vidéo tourne, on laisse le thread principal gérer les antennes
503
+ if video_playing or emotion is None:
504
+ current = None
505
+ time.sleep(0.05)
506
+ continue
507
+
508
+ if emotion != current:
509
+ current = emotion
510
+ t_start = time.time()
511
+ colere_last = time.time() if emotion == "COLERE" else 0.0
512
+
513
+ t_state = time.time() - t_start
514
+
515
+ # Calcul des antennes selon l'émotion
516
+ if emotion == "COLERE":
517
+ elapsed_since_last = time.time() - colere_last
518
+ if elapsed_since_last >= COLERE_PERIOD:
519
+ colere_last = time.time()
520
+ elapsed_since_last = 0.0
521
+
522
+ anim_t = elapsed_since_last # temps dans le cycle
523
+ if anim_t < COLERE_ANIM:
524
+ # Animation colère
525
+ if anim_t < 0.15:
526
+ angle = -1.0 * (anim_t / 0.15) * ANTENNA_MAX_col
527
+ elif anim_t < 0.40:
528
+ angle = (-1.0 + 1.0 * ((anim_t - 0.15) / 0.15)) * ANTENNA_MAX_col
529
+ elif anim_t < 0.65:
530
+ angle = -1.0 * ((anim_t - 0.3) / 0.15) * ANTENNA_MAX_col
531
+ else:
532
+ angle = -1.0 * ANTENNA_MAX_col
533
+ left_ant = angle
534
+ right_ant = -angle
535
+ else:
536
+ # Entre les répétitions : position neutre
537
+ neutral_t = anim_t - COLERE_ANIM
538
+ angle = 1.0 * (1.0 - math.exp(-14.0 * neutral_t)) * ANTENNA_MAX_alt
539
+ left_ant = -(angle - 0.3)
540
+ right_ant = (angle - 0.3)
541
+ elif emotion == "JOIE":
542
+ oscillation = 0.5 * math.sin(2 * math.pi * 4.0 * t_state)
543
+ angle = (0.5 + oscillation) * ANTENNA_MAX_alt
544
+ left_ant = -angle
545
+ right_ant = angle
546
+ elif emotion == "CONTENT":
547
+ oscillation = 0.8 * math.sin(2 * math.pi * 0.5 * t_state)
548
+ angle = oscillation * ANTENNA_MAX_alt
549
+ left_ant = right_ant = angle
550
+ elif emotion == "SURPRISE":
551
+ angle = 1.0 * (1.0 - math.exp(-14.0 * t_state)) * ANTENNA_MAX_alt
552
+ left_ant = -(angle - 0.8)
553
+ right_ant = (angle - 0.8)
554
+ elif emotion == "TRISTESSE":
555
+ target = -1.0 * (1.0 - math.exp(-1.5 * t_state)) * ANTENNA_MAX
556
+ left_ant = target - 0.2
557
+ right_ant = -(target - 0.2)
558
+ elif emotion == "PEUR":
559
+ tremblement = 0.1 * math.sin(2 * math.pi * 15.0 * t_state)
560
+ angle = -1.0 * (1.0 - math.exp(-1.5 * t_state) + tremblement) * ANTENNA_MAX
561
+ left_ant = angle - 0.2
562
+ right_ant = -(angle - 0.2)
563
+ else: # NEUTRE
564
+ angle = 1.0 * (1.0 - math.exp(-14.0 * t_state)) * ANTENNA_MAX_alt
565
+ left_ant = -(angle - 0.3)
566
+ right_ant = (angle - 0.3)
567
+
568
+ clamp = lambda v: float(max(-ANTENNA_MAX, min(ANTENNA_MAX, v)))
569
+ try:
570
+ reachy_mini.set_target(antennas=[clamp(left_ant), clamp(right_ant)])
571
+ except Exception:
572
+ pass
573
+
574
+ time.sleep(0.02)
575
+
576
  def run(self, reachy_mini: ReachyMini, stop_event: threading.Event):
577
  reachy_mini.goto_target(create_head_pose(), antennas=[0.0, 0.0], duration=1.0)
578
  time.sleep(1.2)
579
 
580
+ emotion_thread = threading.Thread(
581
+ target=self._run_manual_emotion_loop,
582
+ args=(reachy_mini, stop_event),
583
+ daemon=True,
584
+ )
585
+ emotion_thread.start()
586
+
587
  while not stop_event.is_set():
588
  with self.playback_lock:
589
  commands_to_play = list(self.current_commands)
jericho/projects/fish/signals.json DELETED
@@ -1,195 +0,0 @@
1
- {
2
- "video": "C:\\Users\\hugov\\reachy_mini\\my_apps\\jericho\\jericho\\projects\\fish\\input.mp4",
3
- "output_video": "C:\\Users\\hugov\\reachy_mini\\my_apps\\jericho\\jericho\\projects\\fish\\output_annotated.mp4",
4
- "fps": 29.97,
5
- "width": 256,
6
- "height": 144,
7
- "total_frames": 6812,
8
- "output_total_frames": 7,
9
- "skipped_no_face_frames": 6800,
10
- "timeline": "compact_face_only",
11
- "frames": [
12
- {
13
- "frame": 0,
14
- "timestamp_s": 0.0,
15
- "face_detected": true,
16
- "face_cx": 0.5041,
17
- "face_cy": 0.5993,
18
- "face_w": 0.1426,
19
- "face_h": 0.2724,
20
- "bbox_xmin": 0.4328,
21
- "bbox_ymin": 0.4631,
22
- "bbox_xmax": 0.5754,
23
- "bbox_ymax": 0.7355,
24
- "yaw": 20.61,
25
- "pitch": -5.88,
26
- "roll": -24.4,
27
- "ear_left": 0.3552,
28
- "ear_right": 0.3423,
29
- "mar": 0.4183,
30
- "smile": 0.9454,
31
- "brow_raise_left": 0.0973,
32
- "brow_raise_right": 0.1283,
33
- "brow_furrow": 0.3575,
34
- "emotion": "CONTENT",
35
- "original_frame": 362,
36
- "original_timestamp_s": 12.0787
37
- },
38
- {
39
- "frame": 1,
40
- "timestamp_s": 0.0334,
41
- "face_detected": true,
42
- "face_cx": 0.4867,
43
- "face_cy": 0.5864,
44
- "face_w": 0.1723,
45
- "face_h": 0.2947,
46
- "bbox_xmin": 0.4005,
47
- "bbox_ymin": 0.439,
48
- "bbox_xmax": 0.5729,
49
- "bbox_ymax": 0.7337,
50
- "yaw": 19.71,
51
- "pitch": -2.08,
52
- "roll": -39.22,
53
- "ear_left": 0.3216,
54
- "ear_right": 0.3117,
55
- "mar": 0.3816,
56
- "smile": 0.7823,
57
- "brow_raise_left": 0.0676,
58
- "brow_raise_right": 0.1177,
59
- "brow_furrow": 0.3827,
60
- "emotion": "NEUTRE",
61
- "original_frame": 2441,
62
- "original_timestamp_s": 81.448
63
- },
64
- {
65
- "frame": 2,
66
- "timestamp_s": 0.0667,
67
- "face_detected": true,
68
- "face_cx": 0.4762,
69
- "face_cy": 0.5905,
70
- "face_w": 0.1745,
71
- "face_h": 0.2462,
72
- "bbox_xmin": 0.3889,
73
- "bbox_ymin": 0.4674,
74
- "bbox_xmax": 0.5634,
75
- "bbox_ymax": 0.7136,
76
- "yaw": 43.6,
77
- "pitch": 9.84,
78
- "roll": -60.06,
79
- "ear_left": 0.3956,
80
- "ear_right": 0.3608,
81
- "mar": 0.3857,
82
- "smile": 1.0639,
83
- "brow_raise_left": 0.0065,
84
- "brow_raise_right": 0.0484,
85
- "brow_furrow": 0.5031,
86
- "emotion": "JOIE",
87
- "original_frame": 2501,
88
- "original_timestamp_s": 83.45
89
- },
90
- {
91
- "frame": 3,
92
- "timestamp_s": 0.1001,
93
- "face_detected": true,
94
- "face_cx": 0.4605,
95
- "face_cy": 0.5879,
96
- "face_w": 0.1804,
97
- "face_h": 0.2179,
98
- "bbox_xmin": 0.3703,
99
- "bbox_ymin": 0.4789,
100
- "bbox_xmax": 0.5508,
101
- "bbox_ymax": 0.6969,
102
- "yaw": 51.33,
103
- "pitch": 1.17,
104
- "roll": -72.7,
105
- "ear_left": 0.4808,
106
- "ear_right": 0.5752,
107
- "mar": 0.3477,
108
- "smile": 0.8995,
109
- "brow_raise_left": 0.0225,
110
- "brow_raise_right": 0.0464,
111
- "brow_furrow": 0.4005,
112
- "emotion": "PEUR",
113
- "original_frame": 5331,
114
- "original_timestamp_s": 177.8777
115
- },
116
- {
117
- "frame": 4,
118
- "timestamp_s": 0.1335,
119
- "face_detected": true,
120
- "face_cx": 0.4809,
121
- "face_cy": 0.604,
122
- "face_w": 0.1627,
123
- "face_h": 0.2473,
124
- "bbox_xmin": 0.3996,
125
- "bbox_ymin": 0.4803,
126
- "bbox_xmax": 0.5622,
127
- "bbox_ymax": 0.7276,
128
- "yaw": 28.46,
129
- "pitch": -2.9,
130
- "roll": -43.45,
131
- "ear_left": 0.3534,
132
- "ear_right": 0.4038,
133
- "mar": 0.4107,
134
- "smile": 0.8494,
135
- "brow_raise_left": 0.0586,
136
- "brow_raise_right": 0.0748,
137
- "brow_furrow": 0.4035,
138
- "emotion": "NEUTRE",
139
- "original_frame": 5662,
140
- "original_timestamp_s": 188.9221
141
- },
142
- {
143
- "frame": 5,
144
- "timestamp_s": 0.1668,
145
- "face_detected": true,
146
- "face_cx": 0.4915,
147
- "face_cy": 0.6195,
148
- "face_w": 0.1447,
149
- "face_h": 0.2324,
150
- "bbox_xmin": 0.4192,
151
- "bbox_ymin": 0.5033,
152
- "bbox_xmax": 0.5639,
153
- "bbox_ymax": 0.7357,
154
- "yaw": 27.66,
155
- "pitch": -3.79,
156
- "roll": -36.34,
157
- "ear_left": 0.3412,
158
- "ear_right": 0.448,
159
- "mar": 0.3767,
160
- "smile": 0.764,
161
- "brow_raise_left": 0.0665,
162
- "brow_raise_right": 0.099,
163
- "brow_furrow": 0.3344,
164
- "emotion": "NEUTRE",
165
- "original_frame": 6053,
166
- "original_timestamp_s": 201.9684
167
- },
168
- {
169
- "frame": 6,
170
- "timestamp_s": 0.2002,
171
- "face_detected": true,
172
- "face_cx": 0.4813,
173
- "face_cy": 0.5952,
174
- "face_w": 0.1594,
175
- "face_h": 0.2209,
176
- "bbox_xmin": 0.4016,
177
- "bbox_ymin": 0.4848,
178
- "bbox_xmax": 0.561,
179
- "bbox_ymax": 0.7057,
180
- "yaw": 45.55,
181
- "pitch": 5.15,
182
- "roll": -64.75,
183
- "ear_left": 0.3412,
184
- "ear_right": 0.4439,
185
- "mar": 0.3823,
186
- "smile": 0.8951,
187
- "brow_raise_left": 0.0063,
188
- "brow_raise_right": 0.0339,
189
- "brow_furrow": 0.4389,
190
- "emotion": "NEUTRE",
191
- "original_frame": 6777,
192
- "original_timestamp_s": 226.1259
193
- }
194
- ]
195
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
jericho/projects/fish/status.txt DELETED
@@ -1 +0,0 @@
1
- ERROR: NO_FACE_DETECTED
 
 
jericho/projects/hugo/signals.json DELETED
The diff for this file is too large to render. See raw diff
 
jericho/projects/hugo/status.txt DELETED
@@ -1 +0,0 @@
1
- DONE
 
 
jericho/static/index.html CHANGED
@@ -10,7 +10,7 @@
10
  <header>
11
  <h1>Jericho projet : Reachy Mini</h1>
12
  <div id="conteneurHeader">
13
- <img src="./image/logoReachyMini.png" alt="Photo de Reachy" width="10%" />
14
  <div id="robot-status">Prêt</div>
15
  </div>
16
  </header>
@@ -51,6 +51,33 @@
51
  </p>
52
  </div>
53
  </section>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
54
  </main>
55
 
56
  <script src="/static/main.js"></script>
 
10
  <header>
11
  <h1>Jericho projet : Reachy Mini</h1>
12
  <div id="conteneurHeader">
13
+ <img src="static/images/logoReachyMini.png" alt="Photo de Reachy" width="10%" />
14
  <div id="robot-status">Prêt</div>
15
  </div>
16
  </header>
 
51
  </p>
52
  </div>
53
  </section>
54
+
55
+ <section class="card">
56
+ <h2>Les émotions</h2>
57
+ <div id="listeEmotions">
58
+ <button id="joie">
59
+ Joie
60
+ </button>
61
+ <button id="content">
62
+ Content
63
+ </button>
64
+ <button id="surprise">
65
+ Surprise
66
+ </button>
67
+ <button id="colere">
68
+ Colère
69
+ </button>
70
+ <button id="peur">
71
+ Peur
72
+ </button>
73
+ <button id="tristesse">
74
+ Tristesse
75
+ </button>
76
+ <button id="neutre">
77
+ Neutre
78
+ </button>
79
+ </div>
80
+ </section>
81
  </main>
82
 
83
  <script src="/static/main.js"></script>
jericho/static/main.js CHANGED
@@ -70,6 +70,25 @@ document.addEventListener('DOMContentLoaded', () => {
70
  if (currentLoadedProject) videoPlayer.play();
71
  };
72
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
73
  });
74
 
75
  document.getElementById('import-btn').onclick = function() {
@@ -208,6 +227,14 @@ window.loadVideoPlayer = async function(name, url) {
208
  await sendPlayback('stop', { projectName: currentLoadedProject, playing: false });
209
  }
210
 
 
 
 
 
 
 
 
 
211
  currentLoadedProject = name;
212
  document.getElementById('player-section').classList.remove('hidden');
213
  document.getElementById('current-video-title').textContent = 'Projet : ' + name;
@@ -254,4 +281,4 @@ videoPlayer.addEventListener('ended', () => {
254
  robotStatus.textContent = 'Terminé';
255
  sendPlayback('stop', { playing: false });
256
  stopSyncLoop();
257
- });
 
70
  if (currentLoadedProject) videoPlayer.play();
71
  };
72
  }
73
+
74
+ // Boutons d'émotions
75
+ const emotionIds = ['joie', 'content', 'surprise', 'colere', 'peur', 'tristesse', 'neutre'];
76
+ emotionIds.forEach(id => {
77
+ const el = document.getElementById(id);
78
+ if (!el) return;
79
+ el.addEventListener('click', async () => {
80
+ // Highlight du bouton actif
81
+ emotionIds.forEach(eid => document.getElementById(eid)?.classList.remove('active-emotion'));
82
+ el.classList.add('active-emotion');
83
+
84
+ await fetch('/api/emotion', {
85
+ method: 'POST',
86
+ headers: { 'Content-Type': 'application/json' },
87
+ body: JSON.stringify({ emotion: id.toUpperCase() })
88
+ });
89
+ robotStatus.textContent = `Émotion : ${el.textContent.trim()}`;
90
+ });
91
+ });
92
  });
93
 
94
  document.getElementById('import-btn').onclick = function() {
 
227
  await sendPlayback('stop', { projectName: currentLoadedProject, playing: false });
228
  }
229
 
230
+ // Annuler l'émotion manuelle active
231
+ document.querySelectorAll('#listeEmotions button').forEach(b => b.classList.remove('active-emotion'));
232
+ await fetch('/api/emotion', {
233
+ method: 'POST',
234
+ headers: { 'Content-Type': 'application/json' },
235
+ body: JSON.stringify({ emotion: null })
236
+ });
237
+
238
  currentLoadedProject = name;
239
  document.getElementById('player-section').classList.remove('hidden');
240
  document.getElementById('current-video-title').textContent = 'Projet : ' + name;
 
281
  robotStatus.textContent = 'Terminé';
282
  sendPlayback('stop', { playing: false });
283
  stopSyncLoop();
284
+ });
jericho/static/style.css CHANGED
@@ -122,3 +122,21 @@ input[type="text"], input[type="number"] {
122
  color: #74424a;
123
  margin-bottom: 0;
124
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
122
  color: #74424a;
123
  margin-bottom: 0;
124
  }
125
+
126
+ #listeEmotions{
127
+ button{
128
+ color : #a34952;
129
+ background-color: white;
130
+ font-family:'Trebuchet MS', 'Lucida Sans Unicode', 'Lucida Grande', 'Lucida Sans', Arial, sans-serif;
131
+ font-weight: lighter;
132
+ border-color: #ffffff;
133
+ border-radius: 10%;
134
+ margin-top : auto;
135
+ margin-bottom: auto;
136
+ padding-top : 0.7%;
137
+ padding-bottom : 0.7%;
138
+ font-size: 1.3em;
139
+ margin-right : 2.5%;
140
+ width : 11%;
141
+ }
142
+ }