Files changed (1) hide show
  1. index.html +449 -429
index.html CHANGED
@@ -1,430 +1,450 @@
1
- <!DOCTYPE html>
2
- <html lang="en">
3
-
4
- <head>
5
- <meta charset="UTF-8">
6
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
7
- <title>Motion Capture Visualization</title>
8
- <script src="https://cdn.plot.ly/plotly-latest.min.js"></script>
9
- <script src="https://cdnjs.cloudflare.com/ajax/libs/PapaParse/5.3.0/papaparse.min.js"></script>
10
- <link href="https://fonts.googleapis.com/css2?family=Orbitron:wght@400;700&display=swap" rel="stylesheet">
11
- <!--'Orbitron', sans-serif;Arial, Helvetica, sans-serif-->
12
- <style>
13
- body {
14
- margin: 0;
15
- padding: 20px;
16
- font-family: 'Orbitron', 'Arial', sans-serif;
17
- background-color: #1a1a1a;
18
- color: #ffffff;
19
- min-height: 90vh;
20
- }
21
-
22
- h1 {
23
- width: 1280px;
24
- color: #ffffff;
25
- margin: 0 auto 30px;
26
- font-size: 30px;
27
- text-shadow: 2px 2px 4px rgba(255, 255, 255, 0.5)
28
- }
29
-
30
- .main-container {
31
- display: grid;
32
- grid-template-columns: 250px 1fr;
33
- gap: 20px;
34
- max-width: 1280px;
35
- margin: 0 auto;
36
- height: calc(100vh - 80px);
37
- }
38
-
39
- /* Panel A: Episodes List */
40
- #episodes-container {
41
- background-color: #2a2a2a;
42
- border-radius: 10px;
43
- padding: 15px;
44
- height: 90%;
45
- overflow-y: auto;
46
- }
47
-
48
- #episodes-title {
49
- color: #ffffff;
50
- margin-top: 0;
51
- padding-bottom: 10px;
52
- border-bottom: 1px solid #3a3a3a;
53
- }
54
-
55
- #episodes-grid {
56
- display: flex;
57
- flex-direction: column;
58
- gap: 4px;
59
- margin-top: 15px;
60
- }
61
-
62
- .episode-radio {
63
- padding: 8px;
64
- border-radius: 6px;
65
- transition: background-color 0.2s;
66
- }
67
-
68
- .episode-radio:hover {
69
- background-color: #3a3a3a;
70
- }
71
-
72
- .episode-radio input[type="radio"] {
73
- margin-right: 10px;
74
- }
75
-
76
- .episode-radio label {
77
- cursor: pointer;
78
- }
79
-
80
- /* Right Content Container */
81
- .content-container {
82
- display: grid;
83
- grid-template-columns: 1fr;
84
- grid-template-rows: auto 1fr;
85
- gap: 20px;
86
- height: 100%;
87
- }
88
-
89
- /* Video Container */
90
- .video-container {
91
- display: flex;
92
- flex-direction: column;
93
- align-items: center;
94
- background-color: #2a2a2a;
95
- border-radius: 10px;
96
- padding: 22px;
97
- height: fit-content;
98
- }
99
-
100
- video {
101
- width: 720px;
102
- border-radius: 6px;
103
- margin-bottom: 15px;
104
- background-color: #000;
105
- }
106
-
107
- .controls {
108
- display: flex;
109
- gap: 10px;
110
- justify-content: center;
111
- padding: 10px 0;
112
- }
113
-
114
- .controls button {
115
- background-color: #3a3a3a;
116
- border: none;
117
- border-radius: 6px;
118
- padding: 4px 8px;
119
- cursor: pointer;
120
- transition: background-color 0.2s;
121
- font-size: 18px;
122
- }
123
-
124
- .controls button:hover {
125
- background-color: #4a4a4a;
126
- }
127
-
128
- /* Plot Container */
129
- #plotDiv {
130
- background-color: #2a2a2a;
131
- border-radius: 10px;
132
- padding: 15px;
133
- width: 720px;
134
- height: 550px;
135
- }
136
-
137
- #loadingIndicator {
138
- position: fixed;
139
- top: 50%;
140
- left: 50%;
141
- transform: translate(-50%, -50%);
142
- background-color: rgba(0, 0, 0, 0.8);
143
- padding: 20px 40px;
144
- border-radius: 10px;
145
- display: none;
146
- }
147
-
148
- /* Scrollbar Styling */
149
- ::-webkit-scrollbar {
150
- width: 8px;
151
- }
152
-
153
- ::-webkit-scrollbar-track {
154
- background: #2a2a2a;
155
- }
156
-
157
- ::-webkit-scrollbar-thumb {
158
- background: #4a4a4a;
159
- border-radius: 4px;
160
- }
161
-
162
- ::-webkit-scrollbar-thumb:hover {
163
- background: #5a5a5a;
164
- }
165
-
166
- </style>
167
- </head>
168
-
169
- <body>
170
- <h1>Motion Capture Visualization</h1>
171
-
172
- <div class="main-container">
173
- <!-- Panel A: Episodes List -->
174
- <div id="episodes-container">
175
- <h3 id="episodes-title">Episodes</h3>
176
- <div id="episodes-grid"></div>
177
- </div>
178
-
179
- <div class="content-container">
180
- <!-- Panel B: Video Player -->
181
- <div class="video-container">
182
- <video id="laptopVideo">
183
- <source src="https://huggingface.co/datasets/cyberorigin/fold_towels/resolve/main/Video/video.mp4"
184
- type="video/mp4">
185
- Your browser does not support the video tag.
186
- </video>
187
- <div class="controls">
188
- <button id="playPauseBtn">▶️</button>
189
- <button id="rewindBtn">⏪</button>
190
- <button id="forwardBtn">⏩</button>
191
- <button id="restartBtn">↩️</button>
192
- </div>
193
- <!-- Panel C: Plot Visualization -->
194
- <div id="plotDiv"></div>
195
- </div>
196
- </div>
197
- </div>
198
-
199
- <div id="loadingIndicator">Loading...</div>
200
-
201
- <script>
202
- let csvFilePath = 'https://huggingface.co/datasets/cyberorigin/fold_towels/resolve/main/MoCap/mocap.csv';
203
- const body_part_names = [
204
- 'Left Shoulder', 'Right Upper Arm', 'Left Lower Leg', 'Spine1', 'Right Upper Leg',
205
- 'Spine3', 'Right Lower Arm', 'Left Foot', 'Right Lower Leg', 'Right Shoulder',
206
- 'Left Hand', 'Left Upper Leg', 'Right Foot', 'Spine', 'Spine2', 'Left Lower Arm',
207
- 'Left Toe', 'Neck', 'Right Hand', 'Right Toe', 'Head', 'Left Upper Arm', 'Hips'
208
- ];
209
- const laptopVideo = document.getElementById('laptopVideo');
210
- const playPauseBtn = document.getElementById('playPauseBtn');
211
- const rewindBtn = document.getElementById('rewindBtn');
212
- const forwardBtn = document.getElementById('forwardBtn');
213
- const restartBtn = document.getElementById('restartBtn');
214
- const radioButtons = document.querySelectorAll('input[name="videoOption"]');
215
- const episodeContainer = document.getElementById('episodes-container');
216
- const episodesGrid = document.getElementById('episodes-grid');
217
- document.addEventListener('DOMContentLoaded', loadEpisodesCsv);
218
- function loadEpisodesCsv() {
219
- fetch("https://huggingface.co/datasets/cyberorigin/test/resolve/main/episodes.csv")
220
- .then(response => response.text())
221
- .then(data => {
222
- const lines = data.split('\n');
223
- processEpisodes(lines);
224
- })
225
- .catch(error => console.error('Error loading CSV file:', error));
226
- }
227
- function processEpisodes(lines) {
228
- episodesGrid.innerHTML = '';
229
- lines.forEach((line, index) => {
230
- if (line.trim() !== '' && index != 0) {
231
- const id = line.split(',')[0];
232
- const episodeNumber = index;
233
-
234
- const radioDiv = document.createElement('div');
235
- radioDiv.className = 'episode-radio';
236
-
237
- const radio = document.createElement('input');
238
- radio.type = 'radio';
239
- radio.id = `episode${episodeNumber}`;
240
- radio.name = 'episodeGroup';
241
- radio.addEventListener('change', () => updateVideoAndCSVSource(id));
242
-
243
- const label = document.createElement('label');
244
- label.htmlFor = `episode${episodeNumber}`;
245
- label.textContent = `Episode ${episodeNumber}`;
246
-
247
- radioDiv.appendChild(radio);
248
- radioDiv.appendChild(label);
249
- episodesGrid.appendChild(radioDiv);
250
- }
251
- });
252
- }
253
-
254
-
255
- let totalEpisodes = 100;
256
- // const container = document.getElementById('episodes-container')
257
-
258
- let animationFrameId;
259
- let isPlaying = false;
260
- function togglePlayPause() {
261
- if (!isPlaying) {
262
- laptopVideo.play();
263
- playPauseBtn.textContent = '⏸️';
264
- isPlaying = true;
265
- animate3DVisualization();
266
- } else {
267
- laptopVideo.pause();
268
- playPauseBtn.textContent = '▶️';
269
- isPlaying = false;
270
- cancelAnimationFrame(animationFrameId);
271
- }
272
- }
273
-
274
- function rewind() {
275
- laptopVideo.currentTime -= 5;
276
- update3DVisualization();
277
- }
278
-
279
- function forward() {
280
- laptopVideo.currentTime += 5;
281
- update3DVisualization();
282
- }
283
-
284
- function restart() {
285
- laptopVideo.currentTime = 0;
286
- update3DVisualization();
287
- }
288
- playPauseBtn.addEventListener('click', togglePlayPause);
289
- rewindBtn.addEventListener('click', rewind);
290
- forwardBtn.addEventListener('click', forward);
291
- restartBtn.addEventListener('click', restart);
292
- function getCoordinates(data, coordinate) {
293
- return body_part_names.map(part => parseFloat(data[`${part}_${coordinate}`]));
294
- }
295
- let frames;
296
- function processData(results) {
297
- console.log("Processing data:", results);
298
- const motion_capture_data = results.data.filter((_, index) => index % 3 === 0);
299
- frames = motion_capture_data.map((row, index) => ({
300
- name: index.toString(),
301
- data: [{
302
- x: getCoordinates(row, 'x'),
303
- y: getCoordinates(row, 'y'),
304
- z: getCoordinates(row, 'z'),
305
- mode: 'markers',
306
- type: 'scatter3d',
307
- marker: { size: 4.8, color: "blue" }
308
- }]
309
- }));
310
- if (frames.length === 0) {
311
- console.error("No frames were created from the data");
312
- return;
313
- }
314
- const initialFrame = frames[0].data[0]
315
- // Config canvas layout styles
316
- const layout = {
317
- title: {
318
- text: '3D Motion Capture',
319
- font: {
320
- color: 'white',
321
- size: 20
322
- },
323
- x: 0.5,
324
- y: 1.2
325
- },
326
- paper_bgcolor: 'black',
327
- plot_bgcolor: 'black',
328
- scene: {
329
- xaxis: {
330
- title: 'X',
331
- color: 'white',
332
- gridcolor: 'gray'
333
- },
334
- yaxis: {
335
- title: 'Y',
336
- color: 'white',
337
- gridcolor: 'gray'
338
- },
339
- zaxis: {
340
- title: 'Z',
341
- color: 'white',
342
- gridcolor: 'gray'
343
- },
344
- bgcolor: 'black'
345
- },
346
- font: {color: 'white'},
347
- margin: {
348
- l: 2,
349
- r: 2,
350
- b: 2,
351
- t: 50,
352
- pad: 4,
353
- },
354
- }
355
- Plotly.newPlot('plotDiv', [initialFrame], layout)
356
- }
357
-
358
- function update3DVisualization() {
359
- if (!frames) return
360
- const currentTime = laptopVideo.currentTime
361
- const totalDuration = laptopVideo.duration
362
- const frameIndex = Math.floor((currentTime / totalDuration) * frames.length)
363
- const frame = frames[Math.min(frameIndex, frames.length - 1)]
364
- Plotly.animate('plotDiv', frame, {
365
- transition: {duration: 0},
366
- frame: {duration: 0, redraw: true},
367
- })
368
- }
369
-
370
- function animate3DVisualization() {
371
- update3DVisualization()
372
- if (isPlaying) {
373
- animationFrameId = requestAnimationFrame(animate3DVisualization)
374
- }
375
- }
376
-
377
- //function updateVideoAndCSVSource() {
378
- //const selectedOption = document.querySelector('input[name="videoOption"]:checked').value;
379
- //const videoUrl = `https://huggingface.co/datasets/cyberorigin/${selectedOption}/resolve/main/Video/video.mp4`;
380
- //if (selectedOption != "twist_the_tube") {
381
- // csvFilePath = `https://huggingface.co/datasets/cyberorigin/${selectedOption}/resolve/main/MoCap/mocap.csv`;
382
- //}
383
- //else {
384
- // csvFilePath = `https://huggingface.co/datasets/cyberorigin/${selectedOption}/resolve/main/MoCap/MoCap.csv`;
385
- //}
386
- function updateVideoAndCSVSource(id) {
387
- const selectedOption = document.querySelector('input[name="episodeGroup"]:checked').value;
388
- const videoUrl = `https://huggingface.co/datasets/cyberorigin/pick_and_place/resolve/main/color/${id}.mp4`;
389
- csvFilePath = `https://huggingface.co/datasets/cyberorigin/pick_and_place/resolve/main/motion_capture/${id}.csv`;
390
-
391
- laptopVideo.pause();
392
-
393
- laptopVideo.querySelector('source').src = videoUrl;
394
-
395
- laptopVideo.load();
396
-
397
- isPlaying = false;
398
- playPauseBtn.textContent = '▶️';
399
- // Fetch and process the new CSV data
400
- fetchAndProcessActionCSV();
401
- }
402
-
403
- function fetchAndProcessActionCSV() {
404
- fetch(csvFilePath)
405
- .then(response => {
406
- if (!response.ok) {
407
- throw new Error(`HTTP error! status: ${response.status}`);
408
- }
409
- return response.text();
410
- })
411
- .then(csvString => {
412
- console.log("CSV data loaded successfully");
413
- Papa.parse(csvString, {
414
- header: true,
415
- dynamicTyping: true,
416
- complete: processData
417
- });
418
- })
419
- .catch(error => console.error('Error loading the CSV file:', error));
420
- }
421
-
422
- //radioButtons.forEach(radio => {
423
- // radio.addEventListener('change', updateVideoAndCSVSource);
424
- //});
425
- // Initial CSV fetch and processing
426
- fetchAndProcessActionCSV();
427
- </script>
428
- </body>
429
-
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
430
  </html>
 
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+
4
+ <head>
5
+ <meta charset="UTF-8">
6
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
7
+ <title>Motion Capture Visualization</title>
8
+ <script src="https://cdn.plot.ly/plotly-latest.min.js"></script>
9
+ <script src="https://cdnjs.cloudflare.com/ajax/libs/PapaParse/5.3.0/papaparse.min.js"></script>
10
+ <link href="https://fonts.googleapis.com/css2?family=Orbitron:wght@400;700&display=swap" rel="stylesheet">
11
+ <!--'Orbitron', sans-serif;Arial, Helvetica, sans-serif-->
12
+ <style>
13
+ body {
14
+ font-family: 'Orbitron', sans-serif;
15
+ margin: 0;
16
+ padding: 20px;
17
+ display: flex;
18
+ flex-direction: column;
19
+ align-items: center;
20
+ background-color: #000;
21
+ color: #fff;
22
+ }
23
+
24
+ h1 {
25
+ color: gold;
26
+ text-shadow: 2px 2px 4px rgba(255, 215, 0, 0.5);
27
+ }
28
+
29
+ .main-container {
30
+ display: flex;
31
+ width: 100%;
32
+ max-width: 1500px;
33
+ margin-top: 100px;
34
+ }
35
+
36
+ .episode-list {
37
+ width: 150px;
38
+ margin-right: 0px;
39
+ }
40
+
41
+ .episode-list label {
42
+ display: block;
43
+ margin-bottom: 10px;
44
+ color: #ddd;
45
+ }
46
+
47
+ .content-container {
48
+ display: flex;
49
+ justify-content: space-between;
50
+ flex-grow: 1;
51
+ margin-left: 120px;
52
+ }
53
+
54
+ .video-container {
55
+ width: 58%;
56
+ }
57
+
58
+ #plotDiv {
59
+ width: 460px;
60
+ height: 485px;
61
+ }
62
+
63
+ #episodes-container {
64
+ max-height: 500px;
65
+ width: 210px;
66
+ overflow-y: auto;
67
+ overflow-x: hidden;
68
+ border: 1px solid #ccc;
69
+ border-radius: 5px;
70
+ background-color: #222;
71
+ margin-left: 10px;
72
+ }
73
+
74
+ #episodes-title {
75
+ background-color: #222;
76
+ color: #fff;
77
+ padding: 10px;
78
+ margin: 0;
79
+ position: sticky;
80
+ top: 0;
81
+ z-index: 1;
82
+ }
83
+
84
+ #episodes-grid {
85
+ display: grid;
86
+ grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));
87
+ gap: 2px;
88
+ padding: 10px;
89
+ }
90
+
91
+ .episode-radio {
92
+ display: flex;
93
+ align-items: center;
94
+ }
95
+
96
+ video {
97
+ width: 100%;
98
+ height: auto;
99
+ }
100
+
101
+ .controls {
102
+ display: flex;
103
+ justify-content: center;
104
+ margin-top: 2px;
105
+ }
106
+
107
+ · button {
108
+ margin: 0 5px;
109
+ font-size: 20px;
110
+ background-color: #333;
111
+ color: #fff;
112
+ border: none;
113
+ padding: 5px 10px;
114
+ cursor: pointer;
115
+ }
116
+
117
+ .checkbox-container label {
118
+ display: block;
119
+ margin-bottom: 20px;
120
+ }
121
+
122
+ #loadingIndicator {
123
+ display: none;
124
+ position: fixed;
125
+ top: 50%;
126
+ left: 50%;
127
+ transform: translate(-50%, -50%);
128
+ background-color: rgba(0, 0, 0, 0.7);
129
+ color: rgb(255, 255, 255);
130
+ padding: 20px;
131
+ border-radius: 5px;
132
+ z-index: 1000;
133
+ }
134
+
135
+ .checkbox-list {
136
+ max-height: 400px;
137
+ /* 设置最大高度,超过此高度会出现滚动条 */
138
+ overflow-y: auto;
139
+ /* 添加垂直滚动条 */
140
+ padding-right: 10px;
141
+ /* 为滚动条留出空间 */
142
+ }
143
+
144
+ .checkbox-list label {
145
+ display: block;
146
+ margin-bottom: 0px;
147
+ color: #ddd;
148
+ }
149
+
150
+ .checkbox-list input[type="checkbox"] {
151
+ margin-right: 10px;
152
+ }
153
+
154
+ /* 自定义滚动条样式(针对WebKit浏览器) */
155
+ .checkbox-list::-webkit-scrollbar {
156
+ width: 0px;
157
+ }
158
+
159
+ .checkbox-list::-webkit-scrollbar-track {
160
+ background: #333;
161
+ border-radius: 4px;
162
+ }
163
+
164
+ .checkbox-list::-webkit-scrollbar-thumb {
165
+ background: #666;
166
+ border-radius: 4px;
167
+ }
168
+
169
+ .checkbox-list::-webkit-scrollbar-thumb:hover {
170
+ background: #888;
171
+ }
172
+ </style>
173
+ </head>
174
+
175
+ <body>
176
+ <h1>Motion Capture Visualization</h1>
177
+
178
+ <div class="main-container">
179
+ <!--
180
+ <div class="episode-list">
181
+ <h3>Episodes</h3>
182
+ <label><input type="checkbox"> Episode 1</label>
183
+ <label><input type="checkbox"> Episode 2</label>
184
+ <label><input type="checkbox"> Episode 29</label>
185
+ <label><input type="checkbox"> Episode 30</label>
186
+ </div>
187
+ -->
188
+ <div id="episodes-container">
189
+ <h3 id="episodes-title">Episodes</h3>
190
+ <div id="episodes-grid"></div>
191
+ </div>
192
+ <div class="content-container">
193
+ <!--
194
+ <div class="checkbox-container">
195
+ <h3>Tasks</h3>
196
+ <label>
197
+ <input type="radio" name="videoOption" value="fold_towels" checked> Fold towels
198
+ </label>
199
+ <label>
200
+ <input type="radio" name="videoOption" value="pipette"> Pipette
201
+ </label>
202
+ <label>
203
+ <input type="radio" name="videoOption" value="take_the_item"> Take the item
204
+ </label>
205
+ <label>
206
+ <input type="radio" name="videoOption" value="twist_the_tube"> Twist the tube
207
+ </label>
208
+ </div>-->
209
+
210
+ <div class="video-container">
211
+ <video id="laptopVideo">
212
+ <source src="https://huggingface.co/datasets/cyberorigin/fold_towels/resolve/main/Video/video.mp4"
213
+ type="video/mp4">
214
+ Your browser does not support the video tag.
215
+ </video>
216
+ <div class="controls">
217
+ <button id="playPauseBtn">▶️</button>
218
+ <button id="rewindBtn">⏪</button>
219
+ <button id="forwardBtn">⏩</button>
220
+ <button id="restartBtn">↩️</button>
221
+ </div>
222
+ </div>
223
+ <div id="plotDiv"></div>
224
+ </div>
225
+ </div>
226
+
227
+ <div id="loadingIndicator">Loading...</div>
228
+
229
+ <script>
230
+ let csvFilePath = 'https://huggingface.co/datasets/cyberorigin/fold_towels/resolve/main/MoCap/mocap.csv';
231
+ const body_part_names = [
232
+ 'Left Shoulder', 'Right Upper Arm', 'Left Lower Leg', 'Spine1', 'Right Upper Leg',
233
+ 'Spine3', 'Right Lower Arm', 'Left Foot', 'Right Lower Leg', 'Right Shoulder',
234
+ 'Left Hand', 'Left Upper Leg', 'Right Foot', 'Spine', 'Spine2', 'Left Lower Arm',
235
+ 'Left Toe', 'Neck', 'Right Hand', 'Right Toe', 'Head', 'Left Upper Arm', 'Hips'
236
+ ];
237
+ const laptopVideo = document.getElementById('laptopVideo');
238
+ const playPauseBtn = document.getElementById('playPauseBtn');
239
+ const rewindBtn = document.getElementById('rewindBtn');
240
+ const forwardBtn = document.getElementById('forwardBtn');
241
+ const restartBtn = document.getElementById('restartBtn');
242
+ const radioButtons = document.querySelectorAll('input[name="videoOption"]');
243
+ const episodeContainer = document.getElementById('episodes-container');
244
+ const episodesGrid = document.getElementById('episodes-grid');
245
+ document.addEventListener('DOMContentLoaded', loadEpisodesCsv);
246
+ function loadEpisodesCsv() {
247
+ fetch("https://huggingface.co/datasets/cyberorigin/test/resolve/main/episodes.csv")
248
+ .then(response => response.text())
249
+ .then(data => {
250
+ const lines = data.split('\n');
251
+ processEpisodes(lines);
252
+ })
253
+ .catch(error => console.error('Error loading CSV file:', error));
254
+ }
255
+ function processEpisodes(lines) {
256
+ episodesGrid.innerHTML = '';
257
+ lines.forEach((line, index) => {
258
+ if (line.trim() !== '' && index != 0) {
259
+ const id = line.split(',')[0];
260
+ const episodeNumber = index;
261
+
262
+ const radioDiv = document.createElement('div');
263
+ radioDiv.className = 'episode-radio';
264
+
265
+ const radio = document.createElement('input');
266
+ radio.type = 'radio';
267
+ radio.id = `episode${episodeNumber}`;
268
+ radio.name = 'episodeGroup';
269
+ radio.addEventListener('change', () => updateVideoAndCSVSource(id));
270
+
271
+ const label = document.createElement('label');
272
+ label.htmlFor = `episode${episodeNumber}`;
273
+ label.textContent = `Episode ${episodeNumber}`;
274
+
275
+ radioDiv.appendChild(radio);
276
+ radioDiv.appendChild(label);
277
+ episodesGrid.appendChild(radioDiv);
278
+ }
279
+ });
280
+ }
281
+
282
+
283
+ let totalEpisodes = 100; //获取episode的数量
284
+ // const container = document.getElementById('episodes-container')
285
+ // 循环创建复选框
286
+
287
+ let animationFrameId;
288
+ let isPlaying = false;
289
+ function togglePlayPause() {
290
+ if (!isPlaying) {
291
+ laptopVideo.play();
292
+ playPauseBtn.textContent = '⏸️';
293
+ isPlaying = true;
294
+ animate3DVisualization();
295
+ } else {
296
+ laptopVideo.pause();
297
+ playPauseBtn.textContent = '▶️';
298
+ isPlaying = false;
299
+ cancelAnimationFrame(animationFrameId);
300
+ }
301
+ }
302
+ function rewind() {
303
+ laptopVideo.currentTime -= 5;
304
+ update3DVisualization();
305
+ }
306
+ function forward() {
307
+ laptopVideo.currentTime += 5;
308
+ update3DVisualization();
309
+ }
310
+ function restart() {
311
+ laptopVideo.currentTime = 0;
312
+ update3DVisualization();
313
+ }
314
+ playPauseBtn.addEventListener('click', togglePlayPause);
315
+ rewindBtn.addEventListener('click', rewind);
316
+ forwardBtn.addEventListener('click', forward);
317
+ restartBtn.addEventListener('click', restart);
318
+ function getCoordinates(data, coordinate) {
319
+ return body_part_names.map(part => parseFloat(data[`${part}_${coordinate}`]));
320
+ }
321
+ let frames;
322
+ function processData(results) {
323
+ console.log("Processing data:", results);
324
+ const motion_capture_data = results.data.filter((_, index) => index % 3 === 0);
325
+ frames = motion_capture_data.map((row, index) => ({
326
+ name: index.toString(),
327
+ data: [{
328
+ x: getCoordinates(row, 'x'),
329
+ y: getCoordinates(row, 'y'),
330
+ z: getCoordinates(row, 'z'),
331
+ mode: 'markers',
332
+ type: 'scatter3d',
333
+ marker: { size: 4.8, color: "blue" }
334
+ }]
335
+ }));
336
+ if (frames.length === 0) {
337
+ console.error("No frames were created from the data");
338
+ return;
339
+ }
340
+ const initialFrame = frames[0].data[0];
341
+ const layout = {
342
+ title: {
343
+ text: '3D Motion Capture',
344
+ font: {
345
+ color: 'white',
346
+ size: 20 // 设置字体大小
347
+ },
348
+ x: 0.5, // 设置标题在x轴的位置(0.5表示居中)
349
+ y: 1.2
350
+ },
351
+ paper_bgcolor: 'black',
352
+ plot_bgcolor: 'black',
353
+ scene: {
354
+ xaxis: {
355
+ title: 'X',
356
+ color: 'white',
357
+ gridcolor: 'gray'
358
+ },
359
+ yaxis: {
360
+ title: 'Y',
361
+ color: 'white',
362
+ gridcolor: 'gray'
363
+ },
364
+ zaxis: {
365
+ title: 'Z',
366
+ color: 'white',
367
+ gridcolor: 'gray'
368
+ },
369
+ bgcolor: 'black'
370
+ },
371
+ font: { color: 'white' },
372
+ margin: {
373
+ l: 2, // 左边距
374
+ r: 2, // 右边距
375
+ b: 2, // 底部边距
376
+ t: 50, // 顶部边距
377
+ pad: 4 // 图表内边距
378
+ }
379
+ };
380
+ Plotly.newPlot('plotDiv', [initialFrame], layout);
381
+ }
382
+ function update3DVisualization() {
383
+ if (!frames) return;
384
+ const currentTime = laptopVideo.currentTime;
385
+ const totalDuration = laptopVideo.duration;
386
+ const frameIndex = Math.floor((currentTime / totalDuration) * frames.length);
387
+ const frame = frames[Math.min(frameIndex, frames.length - 1)];
388
+ Plotly.animate('plotDiv', frame, {
389
+ transition: { duration: 0 },
390
+ frame: { duration: 0, redraw: true }
391
+ });
392
+ }
393
+ function animate3DVisualization() {
394
+ update3DVisualization();
395
+ if (isPlaying) {
396
+ animationFrameId = requestAnimationFrame(animate3DVisualization);
397
+ }
398
+ }
399
+ //function updateVideoAndCSVSource() {
400
+ //const selectedOption = document.querySelector('input[name="videoOption"]:checked').value;
401
+ //const videoUrl = `https://huggingface.co/datasets/cyberorigin/${selectedOption}/resolve/main/Video/video.mp4`;
402
+ //if (selectedOption != "twist_the_tube") {
403
+ // csvFilePath = `https://huggingface.co/datasets/cyberorigin/${selectedOption}/resolve/main/MoCap/mocap.csv`;
404
+ //}
405
+ //else {
406
+ // csvFilePath = `https://huggingface.co/datasets/cyberorigin/${selectedOption}/resolve/main/MoCap/MoCap.csv`;
407
+ //}
408
+ function updateVideoAndCSVSource(id) {
409
+ const selectedOption = document.querySelector('input[name="episodeGroup"]:checked').value;
410
+ const videoUrl = `https://huggingface.co/datasets/cyberorigin/pick_and_place/resolve/main/color/${id}.mp4`;
411
+ csvFilePath = `https://huggingface.co/datasets/cyberorigin/pick_and_place/resolve/main/motion_capture/${id}.csv`;
412
+
413
+ laptopVideo.pause();
414
+
415
+ laptopVideo.querySelector('source').src = videoUrl;
416
+
417
+ laptopVideo.load();
418
+
419
+ isPlaying = false;
420
+ playPauseBtn.textContent = '▶️';
421
+ // Fetch and process the new CSV data
422
+ fetchAndProcessActionCSV();
423
+ }
424
+ function fetchAndProcessActionCSV() {
425
+ fetch(csvFilePath)
426
+ .then(response => {
427
+ if (!response.ok) {
428
+ throw new Error(`HTTP error! status: ${response.status}`);
429
+ }
430
+ return response.text();
431
+ })
432
+ .then(csvString => {
433
+ console.log("CSV data loaded successfully");
434
+ Papa.parse(csvString, {
435
+ header: true,
436
+ dynamicTyping: true,
437
+ complete: processData
438
+ });
439
+ })
440
+ .catch(error => console.error('Error loading the CSV file:', error));
441
+ }
442
+ //radioButtons.forEach(radio => {
443
+ // radio.addEventListener('change', updateVideoAndCSVSource);
444
+ //});
445
+ // Initial CSV fetch and processing
446
+ fetchAndProcessActionCSV();
447
+ </script>
448
+ </body>
449
+
450
  </html>