gearmachine commited on
Commit
77a58be
·
1 Parent(s): 4555cad

feat: ドラッグ機能の追加とエラーハンドリングの改善

Browse files

- poseEditorGlobalsにposeDataを追加
- ドラッグ状態の管理を改善し、draggedKeypointを使用
- setupDragEvents関数を実装し、マウスイベントを設定
- キーポイントの位置更新機能を強化
- drawPoseおよびdrawBody関数にハイライト機能を追加
- グローバルposeDataの更新を確実に行うよう修正

Files changed (1) hide show
  1. static/pose_editor.js +277 -106
static/pose_editor.js CHANGED
@@ -4,6 +4,7 @@
4
  window.poseEditorGlobals = {
5
  canvas: null,
6
  ctx: null,
 
7
  isUpdating: false
8
  };
9
 
@@ -40,9 +41,9 @@ const SKELETON_COLORS = [
40
  // キーポイント半径
41
  const KEYPOINT_RADIUS = 4;
42
 
43
- // ドラッグ状態
44
  let isDragging = false;
45
- let draggedPoint = null;
46
  let dragOffset = { x: 0, y: 0 };
47
 
48
  // デバッグログ関数
@@ -86,7 +87,7 @@ function initializePoseEditor() {
86
  debugLog("Canvas initialized successfully");
87
  notifyCanvasStateChange('initialized');
88
 
89
- // ドラッグイベントを設定
90
  setupDragEvents();
91
 
92
  debugLog(`Canvas ready check: canvas=${!!canvas}, ctx=${!!ctx}, isInitialized=${isInitialized}`);
@@ -137,6 +138,259 @@ function showCanvasError(message) {
137
  ctx.fillText(message, canvas.width / 2, canvas.height / 2);
138
  }
139
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
140
  // Canvas状態チェック(refs互換)
141
  function isCanvasReady() {
142
  const ready = window.poseEditorGlobals.canvas && window.poseEditorGlobals.ctx && isInitialized;
@@ -144,8 +398,8 @@ function isCanvasReady() {
144
  return ready;
145
  }
146
 
147
- // ポーズ全体の描画
148
- function drawPose(poseData, enableHands = true, enableFace = true) {
149
  if (!isCanvasReady() || !poseData) return;
150
 
151
  const canvas = window.poseEditorGlobals.canvas;
@@ -159,8 +413,8 @@ function drawPose(poseData, enableHands = true, enableFace = true) {
159
  const scaleX = canvas.width / originalRes[0];
160
  const scaleY = canvas.height / originalRes[1];
161
 
162
- // ボディの描画
163
- drawBody(poseData);
164
 
165
  // 手の描画(座標変換パラメータ付き)
166
  if (enableHands && poseData.hands) {
@@ -173,8 +427,8 @@ function drawPose(poseData, enableHands = true, enableFace = true) {
173
  }
174
  }
175
 
176
- // ボディ描画
177
- function drawBody(poseData) {
178
  if (!poseData.bodies || !poseData.bodies.candidate) {
179
  console.log(`[ERROR] 💥 No bodies data found!`);
180
  return;
@@ -266,9 +520,14 @@ function drawBody(poseData) {
266
  const scaledX = point[0] * scaleX;
267
  const scaledY = point[1] * scaleY;
268
 
269
- // 🔧 refs互換: SKELETON_COLORSの配列ベース色分け
270
- ctx.fillStyle = SKELETON_COLORS[i % SKELETON_COLORS.length];
271
- drawKeypoint(scaledX, scaledY);
 
 
 
 
 
272
  drawnKeypoints++;
273
 
274
  if (i < 5) { // 最初の5つのキーポイントをログ
@@ -532,6 +791,9 @@ window.gradioCanvasUpdate = function(pose_json_str) {
532
  poseData = pose_json_str;
533
  }
534
 
 
 
 
535
  debugLog("gradioCanvasUpdate processing data", poseData);
536
 
537
  if (!isCanvasReady()) {
@@ -575,6 +837,9 @@ window.updatePoseData = function(data, enableHands = true, enableFace = true) {
575
  }
576
 
577
  poseData = data;
 
 
 
578
  drawPose(poseData, enableHands, enableFace);
579
  };
580
 
@@ -652,100 +917,6 @@ function safeCanvasOperation(operation) {
652
  return safeExecute(operation, "Canvas操作中にエラーが発生しました") !== null;
653
  }
654
 
655
- // ドラッグイベントの設定
656
- function setupDragEvents() {
657
- if (!canvas) return;
658
-
659
- canvas.addEventListener('mousedown', onMouseDown);
660
- canvas.addEventListener('mousemove', onMouseMove);
661
- canvas.addEventListener('mouseup', onMouseUp);
662
- canvas.addEventListener('mouseleave', onMouseUp);
663
- }
664
-
665
- // マウスイベントハンドラー
666
- function onMouseDown(event) {
667
- if (!poseData) return;
668
-
669
- const rect = canvas.getBoundingClientRect();
670
- const x = event.clientX - rect.left;
671
- const y = event.clientY - rect.top;
672
-
673
- // 最も近いキーポイントを検索
674
- const nearestPoint = findNearestKeypoint(x, y);
675
- if (nearestPoint && nearestPoint.distance < KEYPOINT_RADIUS * 2) {
676
- isDragging = true;
677
- draggedPoint = nearestPoint;
678
- dragOffset.x = x - nearestPoint.x;
679
- dragOffset.y = y - nearestPoint.y;
680
- canvas.style.cursor = 'grabbing';
681
- notifyCanvasOperation('キーポイントをドラッグ中');
682
- }
683
- }
684
-
685
- function onMouseMove(event) {
686
- if (!isDragging || !draggedPoint) return;
687
-
688
- const rect = canvas.getBoundingClientRect();
689
- const x = event.clientX - rect.left - dragOffset.x;
690
- const y = event.clientY - rect.top - dragOffset.y;
691
-
692
- // キーポイントの位置を更新
693
- updateKeypointPosition(draggedPoint, x, y);
694
-
695
- // 再描画
696
- drawPose(poseData);
697
- }
698
-
699
- function onMouseUp(event) {
700
- if (isDragging) {
701
- isDragging = false;
702
- draggedPoint = null;
703
- canvas.style.cursor = 'crosshair';
704
- notifyCanvasOperation('キーポイントの編集完了');
705
- }
706
- }
707
-
708
- // 最も近いキーポイントを検索
709
- function findNearestKeypoint(x, y) {
710
- if (!poseData || !poseData.bodies || !poseData.bodies.candidate) return null;
711
-
712
- let nearest = null;
713
- let minDistance = Infinity;
714
-
715
- const candidates = poseData.bodies.candidate;
716
- for (let i = 0; i < candidates.length; i++) {
717
- const point = candidates[i];
718
- if (point && point.length >= 2) {
719
- const dx = x - point[0];
720
- const dy = y - point[1];
721
- const distance = Math.sqrt(dx * dx + dy * dy);
722
-
723
- if (distance < minDistance) {
724
- minDistance = distance;
725
- nearest = {
726
- index: i,
727
- x: point[0],
728
- y: point[1],
729
- distance: distance,
730
- type: 'body'
731
- };
732
- }
733
- }
734
- }
735
-
736
- return nearest;
737
- }
738
-
739
- // キーポイント位置の更新
740
- function updateKeypointPosition(pointInfo, newX, newY) {
741
- if (!poseData || !poseData.bodies || !poseData.bodies.candidate) return;
742
-
743
- const candidates = poseData.bodies.candidate;
744
- if (pointInfo.index >= 0 && pointInfo.index < candidates.length) {
745
- candidates[pointInfo.index][0] = newX;
746
- candidates[pointInfo.index][1] = newY;
747
- }
748
- }
749
 
750
  // 🎨 推定接続の描画(少ないキーポイント用の補間機能)
751
  function drawEstimatedConnections(candidates, originalRes, scaleX, scaleY) {
 
4
  window.poseEditorGlobals = {
5
  canvas: null,
6
  ctx: null,
7
+ poseData: null, // 追加!
8
  isUpdating: false
9
  };
10
 
 
41
  // キーポイント半径
42
  const KEYPOINT_RADIUS = 4;
43
 
44
+ // ドラッグ状態(refs互換)
45
  let isDragging = false;
46
+ let draggedKeypoint = -1;
47
  let dragOffset = { x: 0, y: 0 };
48
 
49
  // デバッグログ関数
 
87
  debugLog("Canvas initialized successfully");
88
  notifyCanvasStateChange('initialized');
89
 
90
+ // ドラッグイベントを設定(refs互換)
91
  setupDragEvents();
92
 
93
  debugLog(`Canvas ready check: canvas=${!!canvas}, ctx=${!!ctx}, isInitialized=${isInitialized}`);
 
138
  ctx.fillText(message, canvas.width / 2, canvas.height / 2);
139
  }
140
 
141
+ // ドラッグイベント設定(refs互換)
142
+ function setupDragEvents() {
143
+ debugLog("setupDragEvents called");
144
+
145
+ if (!canvas) {
146
+ debugLog("Canvas not available for drag setup");
147
+ return;
148
+ }
149
+
150
+ debugLog(`Setting up events on canvas: ${canvas.id}, width: ${canvas.width}, height: ${canvas.height}`);
151
+
152
+ canvas.addEventListener('mousedown', handleMouseDown);
153
+ canvas.addEventListener('mousemove', handleMouseMove);
154
+ canvas.addEventListener('mouseup', handleMouseUp);
155
+ canvas.addEventListener('mouseleave', handleMouseUp); // Canvas外ドラッグ対策
156
+
157
+ // テスト用クリックイベント
158
+ canvas.addEventListener('click', function(event) {
159
+ debugLog(`Canvas clicked at: (${event.clientX}, ${event.clientY})`);
160
+ });
161
+
162
+ debugLog("Drag events setup completed");
163
+ }
164
+
165
+ // マウス座標取得(refs互換)
166
+ function getMousePos(event) {
167
+ const rect = canvas.getBoundingClientRect();
168
+ return {
169
+ x: event.clientX - rect.left,
170
+ y: event.clientY - rect.top
171
+ };
172
+ }
173
+
174
+ // 最寄りのキーポイントを検索(refs互換:戻り値はインデックス)
175
+ function findNearestKeypoint(mouseX, mouseY, maxDistance = 20) {
176
+ debugLog(`findNearestKeypoint called: (${mouseX}, ${mouseY})`);
177
+
178
+ // グローバルposeDataを参照
179
+ const currentPoseData = window.poseEditorGlobals.poseData || poseData;
180
+
181
+ if (!currentPoseData) {
182
+ debugLog("No poseData available (global or local)");
183
+ return -1;
184
+ }
185
+
186
+ if (!currentPoseData.bodies) {
187
+ debugLog("No poseData.bodies available");
188
+ return -1;
189
+ }
190
+
191
+ if (!currentPoseData.bodies.candidate) {
192
+ debugLog("No poseData.bodies.candidate available");
193
+ return -1;
194
+ }
195
+
196
+ debugLog(`poseData.bodies.candidate length: ${currentPoseData.bodies.candidate.length}`);
197
+
198
+ const candidates = currentPoseData.bodies.candidate;
199
+ let nearestIndex = -1;
200
+ let minDistance = maxDistance; // refs互換の閾値
201
+
202
+ // 📐 解像度情報の取得
203
+ const originalRes = currentPoseData.resolution || [512, 512];
204
+ const scaleX = canvas.width / originalRes[0];
205
+ const scaleY = canvas.height / originalRes[1];
206
+
207
+ for (let i = 0; i < Math.min(20, candidates.length); i++) { // つま先込み20個
208
+ const point = candidates[i];
209
+
210
+ if (point && point[0] > 1 && point[1] > 1 &&
211
+ point[0] < originalRes[0] && point[1] < originalRes[1]) {
212
+
213
+ // 座標変換を適用
214
+ const scaledX = point[0] * scaleX;
215
+ const scaledY = point[1] * scaleY;
216
+
217
+ const distance = Math.sqrt((mouseX - scaledX) ** 2 + (mouseY - scaledY) ** 2);
218
+
219
+ if (distance < minDistance) {
220
+ minDistance = distance;
221
+ nearestIndex = i;
222
+ }
223
+ }
224
+ }
225
+
226
+ debugLog(`Nearest keypoint: index=${nearestIndex}, distance=${minDistance.toFixed(1)}`);
227
+ return nearestIndex; // 🔧 refs互換:インデックス数値を返す
228
+ }
229
+
230
+ // マウスダウン処理(refs互換)
231
+ function handleMouseDown(event) {
232
+ debugLog(`handleMouseDown called: ${event.type}`);
233
+
234
+ if (!isCanvasReady()) {
235
+ debugLog("Canvas not ready for mouse down");
236
+ return;
237
+ }
238
+
239
+ const mousePos = getMousePos(event);
240
+ debugLog(`Mouse position: (${mousePos.x}, ${mousePos.y})`);
241
+
242
+ const keypointIndex = findNearestKeypoint(mousePos.x, mousePos.y);
243
+ debugLog(`Found keypoint index: ${keypointIndex}`);
244
+
245
+ if (keypointIndex >= 0) {
246
+ isDragging = true;
247
+ draggedKeypoint = keypointIndex;
248
+
249
+ // 🔧 refs互換:ドラッグオフセット計算(キーポイントの正確な位置からのオフセット)
250
+ const currentPoseData = window.poseEditorGlobals.poseData || poseData;
251
+ if (currentPoseData && currentPoseData.bodies && currentPoseData.bodies.candidate) {
252
+ const candidates = currentPoseData.bodies.candidate;
253
+ const originalRes = currentPoseData.resolution || [512, 512];
254
+ const scaleX = canvas.width / originalRes[0];
255
+ const scaleY = canvas.height / originalRes[1];
256
+
257
+ const point = candidates[keypointIndex];
258
+ if (point) {
259
+ const keypointX = point[0] * scaleX;
260
+ const keypointY = point[1] * scaleY;
261
+
262
+ dragOffset = {
263
+ x: mousePos.x - keypointX,
264
+ y: mousePos.y - keypointY
265
+ };
266
+
267
+ debugLog(`Drag offset calculated: (${dragOffset.x.toFixed(1)}, ${dragOffset.y.toFixed(1)})`);
268
+ }
269
+ }
270
+
271
+ debugLog(`Drag started: keypoint ${keypointIndex} at (${mousePos.x}, ${mousePos.y})`);
272
+
273
+ // カーソルを変更
274
+ canvas.style.cursor = 'grabbing';
275
+ } else {
276
+ debugLog("No keypoint found near mouse position");
277
+ }
278
+ }
279
+
280
+ // マウス移動処理(refs互換)
281
+ function handleMouseMove(event) {
282
+ if (!isCanvasReady()) return;
283
+
284
+ const mousePos = getMousePos(event);
285
+
286
+ if (isDragging && draggedKeypoint >= 0) {
287
+ // 🔧 refs互換:オフセット考慮の新座標計算
288
+ const newX = mousePos.x - dragOffset.x;
289
+ const newY = mousePos.y - dragOffset.y;
290
+
291
+ debugLog(`Mouse move: raw(${mousePos.x}, ${mousePos.y}), offset(${dragOffset.x.toFixed(1)}, ${dragOffset.y.toFixed(1)}), new(${newX.toFixed(1)}, ${newY.toFixed(1)})`);
292
+
293
+ // ドラッグ中の座標更新(オフセット適用済み座標で)
294
+ updateKeypointPosition(draggedKeypoint, newX, newY);
295
+
296
+ // リアルタイム再描画(ハイライト付き)- グローバルposeDataを使用
297
+ const currentPoseData = window.poseEditorGlobals.poseData || poseData;
298
+ drawPose(currentPoseData, true, true, draggedKeypoint);
299
+
300
+ } else {
301
+ // ホバー時のカーソル変更
302
+ const keypointIndex = findNearestKeypoint(mousePos.x, mousePos.y);
303
+ canvas.style.cursor = keypointIndex >= 0 ? 'grab' : 'default';
304
+ }
305
+ }
306
+
307
+ // マウスアップ処理(refs互換)
308
+ function handleMouseUp(event) {
309
+ if (isDragging) {
310
+ debugLog(`Drag ended: keypoint ${draggedKeypoint}`);
311
+
312
+ isDragging = false;
313
+ draggedKeypoint = -1;
314
+ canvas.style.cursor = 'default';
315
+
316
+ // Gradioに更新データを送信
317
+ sendPoseDataToGradio();
318
+ }
319
+ }
320
+
321
+ // キーポイント座標更新(refs互換)
322
+ function updateKeypointPosition(keypointIndex, canvasX, canvasY) {
323
+ // グローバルposeDataを参照
324
+ const currentPoseData = window.poseEditorGlobals.poseData || poseData;
325
+
326
+ if (!currentPoseData || !currentPoseData.bodies || !currentPoseData.bodies.candidate) {
327
+ debugLog("No poseData available for keypoint update");
328
+ return;
329
+ }
330
+
331
+ // 📐 解像度情報の取得
332
+ const originalRes = currentPoseData.resolution || [512, 512];
333
+ const scaleX = canvas.width / originalRes[0];
334
+ const scaleY = canvas.height / originalRes[1];
335
+
336
+ // 🔧 refs互換:Canvas座標をデータ座標に変換してからクランプ
337
+ const dataX = canvasX / scaleX;
338
+ const dataY = canvasY / scaleY;
339
+
340
+ // 🔧 refs互換:データ座標系でクランプ(0〜解像度内)
341
+ const clampedDataX = Math.max(0, Math.min(originalRes[0], dataX));
342
+ const clampedDataY = Math.max(0, Math.min(originalRes[1], dataY));
343
+
344
+ // candidateリストを更新
345
+ const candidates = currentPoseData.bodies.candidate;
346
+ if (keypointIndex < candidates.length) {
347
+ candidates[keypointIndex][0] = clampedDataX;
348
+ candidates[keypointIndex][1] = clampedDataY;
349
+
350
+ debugLog(`Updated keypoint ${keypointIndex}: canvas(${canvasX.toFixed(1)}, ${canvasY.toFixed(1)}) → data(${clampedDataX.toFixed(1)}, ${clampedDataY.toFixed(1)})`);
351
+
352
+ // 🔧 ローカルposeDataも同期更新
353
+ if (poseData && poseData.bodies && poseData.bodies.candidate) {
354
+ poseData.bodies.candidate[keypointIndex][0] = clampedDataX;
355
+ poseData.bodies.candidate[keypointIndex][1] = clampedDataY;
356
+ }
357
+ }
358
+ }
359
+
360
+ // Gradioにデータ送信(refs互換)
361
+ function sendPoseDataToGradio() {
362
+ // グローバルposeDataを参照
363
+ const currentPoseData = window.poseEditorGlobals.poseData || poseData;
364
+
365
+ if (!currentPoseData) {
366
+ debugLog("No poseData available for Gradio send");
367
+ return;
368
+ }
369
+
370
+ try {
371
+ // JSON文字列化
372
+ const jsonString = JSON.stringify(currentPoseData);
373
+
374
+ // Gradioのテキストボックスを探して更新
375
+ const textareas = document.querySelectorAll('textarea');
376
+
377
+ for (const textarea of textareas) {
378
+ const currentValue = textarea.value || '';
379
+ if (currentValue.includes('bodies') || currentValue.includes('candidate') || currentValue.trim() === '') {
380
+ textarea.value = jsonString;
381
+ textarea.dispatchEvent(new Event('input', { bubbles: true }));
382
+ debugLog('Pose data sent to Gradio successfully');
383
+ return;
384
+ }
385
+ }
386
+
387
+ debugLog('No suitable Gradio textarea found');
388
+
389
+ } catch (error) {
390
+ debugLog(`Error sending data to Gradio: ${error.message}`);
391
+ }
392
+ }
393
+
394
  // Canvas状態チェック(refs互換)
395
  function isCanvasReady() {
396
  const ready = window.poseEditorGlobals.canvas && window.poseEditorGlobals.ctx && isInitialized;
 
398
  return ready;
399
  }
400
 
401
+ // ポーズ全体の描画(ハイライト対応)
402
+ function drawPose(poseData, enableHands = true, enableFace = true, highlightIndex = -1) {
403
  if (!isCanvasReady() || !poseData) return;
404
 
405
  const canvas = window.poseEditorGlobals.canvas;
 
413
  const scaleX = canvas.width / originalRes[0];
414
  const scaleY = canvas.height / originalRes[1];
415
 
416
+ // ボディの描画(ハイライト対応)
417
+ drawBody(poseData, highlightIndex);
418
 
419
  // 手の描画(座標変換パラメータ付き)
420
  if (enableHands && poseData.hands) {
 
427
  }
428
  }
429
 
430
+ // ボディ描画(ハイライト対応)
431
+ function drawBody(poseData, highlightIndex = -1) {
432
  if (!poseData.bodies || !poseData.bodies.candidate) {
433
  console.log(`[ERROR] 💥 No bodies data found!`);
434
  return;
 
520
  const scaledX = point[0] * scaleX;
521
  const scaledY = point[1] * scaleY;
522
 
523
+ // 🔧 ハイライト対応: ドラッグ中のキーポイントを強調表示
524
+ if (i === highlightIndex) {
525
+ ctx.fillStyle = 'rgb(255,255,0)'; // 黄色でハイライト
526
+ drawKeypoint(scaledX, scaledY, KEYPOINT_RADIUS + 2); // 少し大きく
527
+ } else {
528
+ ctx.fillStyle = SKELETON_COLORS[i % SKELETON_COLORS.length];
529
+ drawKeypoint(scaledX, scaledY);
530
+ }
531
  drawnKeypoints++;
532
 
533
  if (i < 5) { // 最初の5つのキーポイントをログ
 
791
  poseData = pose_json_str;
792
  }
793
 
794
+ // 🔧 グローバルposeDataを必ず更新(ドラッグ機能のため)
795
+ window.poseEditorGlobals.poseData = poseData;
796
+
797
  debugLog("gradioCanvasUpdate processing data", poseData);
798
 
799
  if (!isCanvasReady()) {
 
837
  }
838
 
839
  poseData = data;
840
+ // 🔧 グローバルposeDataも更新(ドラッグ機能のため)
841
+ window.poseEditorGlobals.poseData = poseData;
842
+
843
  drawPose(poseData, enableHands, enableFace);
844
  };
845
 
 
917
  return safeExecute(operation, "Canvas操作中にエラーが発生しました") !== null;
918
  }
919
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
920
 
921
  // 🎨 推定接続の描画(少ないキーポイント用の補間機能)
922
  function drawEstimatedConnections(candidates, originalRes, scaleX, scaleY) {