gearmachine commited on
Commit
65c2919
·
1 Parent(s): be7e89a

feat: 通知機能の改善

Browse files

- 通知設定に情報通知の表示時間とエラー通知の表示時間を追加
- 成功通知、警告通知、エラー通知の各関数で表示時間を設定

Files changed (2) hide show
  1. static/pose_editor.js +25 -147
  2. utils/notifications.py +8 -4
static/pose_editor.js CHANGED
@@ -1,6 +1,6 @@
1
  // Canvas操作用JavaScript for dwpose-editor
2
 
3
- // グローバル変数(refs互換)
4
  window.poseEditorGlobals = {
5
  canvas: null,
6
  ctx: null,
@@ -59,41 +59,19 @@ let isDragging = false;
59
  let draggedKeypoint = -1;
60
  let dragOffset = { x: 0, y: 0 };
61
 
62
- // デバッグログ関数
63
- function debugLog(message) {
64
- // 重要なログのみ出力
65
- const importantPatterns = [
66
- /initializePoseEditor/,
67
- /Failed/,
68
- /Error/,
69
- /Entered rectangle edit mode/,
70
- /Switched to rectangle edit mode/,
71
- /Drag started/,
72
- /drag ended/,
73
- /Canvas initialized/,
74
- /No poseData available/
75
- ];
76
-
77
- const shouldLog = importantPatterns.some(pattern => pattern.test(message));
78
- if (shouldLog) {
79
- console.log(`[DWPose Editor] ${new Date().toISOString()} - ${message}`);
80
- }
81
- }
82
 
83
- // Canvas初期化関数(refs互換)
84
  function initializePoseEditor() {
85
- debugLog("initializePoseEditor called");
86
 
87
  canvas = document.getElementById('pose_canvas');
88
  if (!canvas) {
89
- debugLog("Canvas not found, retrying...");
90
  setTimeout(initializePoseEditor, 100);
91
  return;
92
  }
93
 
94
  ctx = canvas.getContext('2d');
95
  if (!ctx) {
96
- debugLog("Failed to get 2d context");
97
  return;
98
  }
99
 
@@ -116,7 +94,6 @@ function initializePoseEditor() {
116
  canvas.style.cursor = 'default';
117
 
118
  isInitialized = true;
119
- debugLog("Canvas initialized successfully");
120
  notifyCanvasStateChange('initialized');
121
 
122
  // ドラッグイベントを設定(refs互換)
@@ -174,10 +151,8 @@ function showCanvasError(message) {
174
 
175
  // ドラッグイベント設定(refs互換)
176
  function setupDragEvents() {
177
- debugLog("setupDragEvents called");
178
 
179
  if (!canvas) {
180
- debugLog("Canvas not available for drag setup");
181
  return;
182
  }
183
 
@@ -191,14 +166,12 @@ function setupDragEvents() {
191
  canvas.addEventListener('click', function(event) {
192
  });
193
 
194
- debugLog("Drag events setup completed");
195
  }
196
 
197
  // 🔧 Gradioチェックボックス監視設定
198
  function setupGradioCheckboxListeners() {
199
  // チェックボックスとラジオボタン要素を探す(少し待ってから)
200
  setTimeout(() => {
201
- debugLog("🔍 Setting up Gradio component listeners...");
202
 
203
  // チェックボックス監視
204
  const allCheckboxes = document.querySelectorAll('input[type="checkbox"]');
@@ -219,14 +192,12 @@ function setupGradioCheckboxListeners() {
219
 
220
  // ラジオボタン監視(編集モード)
221
  const allRadios = document.querySelectorAll('input[type="radio"]');
222
- debugLog(`🔍 Found ${allRadios.length} radio buttons`);
223
 
224
  allRadios.forEach((radio, index) => {
225
  // 親要素のテキストから簡易モード・詳細モードを特定
226
  const parentText = radio.parentElement?.textContent?.trim() || '';
227
  const grandParentText = radio.parentElement?.parentElement?.textContent?.trim() || '';
228
 
229
- debugLog(`🔍 Radio ${index}: value="${radio.value}", parent="${parentText}", grandparent="${grandParentText}"`);
230
 
231
  if (parentText.includes('簡易モード') || parentText.includes('詳細モード') ||
232
  grandParentText.includes('編集モード')) {
@@ -310,12 +281,10 @@ function findNearestKeypoint(mouseX, mouseY, maxDistance = 20) {
310
  }
311
 
312
  if (!currentPoseData.bodies) {
313
- debugLog("No poseData.bodies available");
314
  return -1;
315
  }
316
 
317
  if (!currentPoseData.bodies.candidate) {
318
- debugLog("No poseData.bodies.candidate available");
319
  return -1;
320
  }
321
 
@@ -457,7 +426,6 @@ function findNearestKeypointInDetailMode(mouseX, mouseY, maxDistance = 15) {
457
  // 詳細モード用:最寄りのキーポイント検索(手・顔の個別キーポイント対応)
458
  function findNearestKeypointInDetailMode(clickX, clickY) {
459
  if (!poseData || !poseData.people || poseData.people.length === 0) {
460
- debugLog("❌ No poseData for detail mode keypoint search");
461
  return null;
462
  }
463
 
@@ -625,7 +593,6 @@ function handleMouseDown(event) {
625
  const currentRect = window.poseEditorGlobals.currentRects[rectType];
626
  if (currentRect) {
627
  window.poseEditorGlobals.originalRect = { ...currentRect };
628
- debugLog(`🔧 Original rect saved for control point drag: ${rectType}`, window.poseEditorGlobals.originalRect);
629
  }
630
 
631
  // コントロールポイントドラッグ(リサイズ)
@@ -641,14 +608,12 @@ function handleMouseDown(event) {
641
  window.poseEditorGlobals.draggedRect = rectType;
642
  window.poseEditorGlobals.dragStartPos = { x: mousePos.x, y: mousePos.y };
643
  isDragging = true;
644
- debugLog(`🔧 Rectangle move drag started: ${rectType}`);
645
  return;
646
  }
647
 
648
  // 他の矩形クリック → 切り替え
649
  if (rectType && rectType !== window.poseEditorGlobals.rectEditMode) {
650
  window.poseEditorGlobals.rectEditMode = rectType;
651
- debugLog(`Switched to rectangle edit mode: ${rectType}`);
652
  // 再描画で新しい矩形のコントロールポイントを表示
653
  const currentPoseData = window.poseEditorGlobals.poseData || poseData;
654
  if (currentPoseData) {
@@ -680,7 +645,6 @@ function handleMouseDown(event) {
680
  window.poseEditorGlobals.draggedRect = null;
681
  window.poseEditorGlobals.dragStartPos = { x: 0, y: 0 };
682
 
683
- debugLog("🔧 Rectangle edit mode ended - all editing states cleared for next editing session");
684
 
685
  // 🚀 矩形位置を保持したまま再描画(再計算させない)
686
  if (currentPoseData) {
@@ -695,12 +659,10 @@ function handleMouseDown(event) {
695
  if (rectType) {
696
  // 矩形モード切り替え時の処理
697
  if (window.poseEditorGlobals.rectEditMode !== rectType) {
698
- debugLog(`💖 Switching rectangle edit mode: ${window.poseEditorGlobals.rectEditMode} → ${rectType}`);
699
  window.poseEditorGlobals.rectEditMode = rectType;
700
  }
701
 
702
  window.poseEditorGlobals.rectEditModeActive = true;
703
- debugLog(`💖 Entered rectangle edit mode: ${rectType}`);
704
 
705
  // 🔧 rectEditInfo全体を初期化(全矩形タイプ対応)
706
  initializeRectEditInfo();
@@ -756,10 +718,8 @@ function handleMouseDown(event) {
756
  }
757
  }
758
 
759
- debugLog(`Drag started: keypoint ${keypointIndex} at (${mousePos.x}, ${mousePos.y})`);
760
  canvas.style.cursor = 'grabbing';
761
  } else {
762
- debugLog("No keypoint found near mouse position");
763
  }
764
  }
765
  }
@@ -770,7 +730,6 @@ function updateDetailKeypointPosition(detailKeypoint, canvasX, canvasY) {
770
  const currentPoseData = window.poseEditorGlobals.poseData || poseData;
771
 
772
  if (!currentPoseData) {
773
- debugLog("No poseData available for detail keypoint update");
774
  return;
775
  }
776
 
@@ -802,7 +761,6 @@ function updateDetailKeypointPosition(detailKeypoint, canvasX, canvasY) {
802
  handData[arrayIndex] = dataX;
803
  handData[arrayIndex + 1] = dataY;
804
  // 信頼度は維持
805
- debugLog(`🫳 Left hand keypoint ${detailKeypoint.index} updated to (${dataX}, ${dataY})`);
806
 
807
  // 元のhandsデータも同期更新
808
  syncHandsToOriginal(currentPoseData, 'left', handData);
@@ -830,7 +788,6 @@ function updateDetailKeypointPosition(detailKeypoint, canvasX, canvasY) {
830
  handData[arrayIndex] = dataX;
831
  handData[arrayIndex + 1] = dataY;
832
  // 信頼度は維持
833
- debugLog(`🫳 Right hand keypoint ${detailKeypoint.index} updated to (${dataX}, ${dataY})`);
834
 
835
  // 元のhandsデータも同期更新
836
  syncHandsToOriginal(currentPoseData, 'right', handData);
@@ -858,7 +815,6 @@ function updateDetailKeypointPosition(detailKeypoint, canvasX, canvasY) {
858
  faceData[arrayIndex] = dataX;
859
  faceData[arrayIndex + 1] = dataY;
860
  // 信頼度は維持
861
- debugLog(`😊 Face keypoint ${detailKeypoint.index} updated to (${dataX}, ${dataY})`);
862
 
863
  // 🚀 元のfacesデータも同期更新
864
  syncFacesToOriginal(currentPoseData, faceData);
@@ -881,7 +837,6 @@ function syncHandsToOriginal(poseData, handType, handData) {
881
  const handIndex = handType === 'left' ? 0 : 1;
882
  if (handIndex < poseData.hands.length) {
883
  poseData.hands[handIndex] = [...handData];
884
- debugLog(`🔄 Hand ${handType} synced to original hands data`);
885
  }
886
  }
887
 
@@ -891,7 +846,6 @@ function syncFacesToOriginal(poseData, faceData) {
891
 
892
  if (poseData.faces.length > 0) {
893
  poseData.faces[0] = [...faceData];
894
- debugLog(`🔄 Face synced to original faces data`);
895
  }
896
  }
897
 
@@ -1047,7 +1001,6 @@ function transformKeypointsInRect(control, newMouseX, newMouseY) {
1047
  // 11. 矩形情報を更新
1048
  control.rect = newRect;
1049
 
1050
- debugLog(`🎯 Transformed ${fieldName} keypoints: scale=(${scaleX.toFixed(2)}, ${scaleY.toFixed(2)})`);
1051
  }
1052
 
1053
  // 🔧 直接矩形変換関数(シンプル版)
@@ -1056,12 +1009,10 @@ function transformKeypointsDirectly(rectType, originalRect, newRect) {
1056
  const originalKeypoints = window.poseEditorGlobals.originalKeypoints;
1057
 
1058
  if (!currentPoseData || !currentPoseData.people || !currentPoseData.people[0]) {
1059
- debugLog("❌ transformKeypointsDirectly: No pose data available");
1060
  return;
1061
  }
1062
 
1063
  if (!originalKeypoints || !originalKeypoints.people || !originalKeypoints.people[0]) {
1064
- debugLog("❌ transformKeypointsDirectly: No original keypoints available");
1065
  return;
1066
  }
1067
 
@@ -1089,23 +1040,17 @@ function transformKeypointsDirectly(rectType, originalRect, newRect) {
1089
  fieldName = 'hand_right_keypoints_2d';
1090
  break;
1091
  default:
1092
- debugLog(`❌ transformKeypointsDirectly: Unknown rectType ${rectType}`);
1093
  return;
1094
  }
1095
 
1096
  if (!targetKeypoints || !Array.isArray(targetKeypoints)) {
1097
- debugLog(`❌ transformKeypointsDirectly: No keypoints for ${fieldName}`);
1098
- debugLog(`🔍 Person keys: ${Object.keys(person)}`);
1099
  return;
1100
  }
1101
 
1102
  if (!originalTargetKeypoints || !Array.isArray(originalTargetKeypoints)) {
1103
- debugLog(`❌ transformKeypointsDirectly: No original keypoints for ${fieldName}`);
1104
- debugLog(`🔍 Original person keys: ${Object.keys(originalPerson)}`);
1105
  return;
1106
  }
1107
 
1108
- debugLog(`✅ transformKeypointsDirectly: Found ${fieldName} - current: ${targetKeypoints.length}, original: ${originalTargetKeypoints.length}`);
1109
 
1110
  // 座標変換設定
1111
  const canvasWidth = window.poseEditorGlobals.canvas ? window.poseEditorGlobals.canvas.width : 512;
@@ -1136,7 +1081,6 @@ function transformKeypointsDirectly(rectType, originalRect, newRect) {
1136
  }
1137
  }
1138
 
1139
- debugLog(`🔧 transformKeypointsDirectly: ${fieldName}, original rect: ${JSON.stringify(originalRect)}, new rect: ${JSON.stringify(newRect)}, normalized: ${isNormalized}`);
1140
 
1141
  // キーポイントを一括変換(元データから毎回変換)
1142
  for (let i = 0; i < originalTargetKeypoints.length; i += 3) {
@@ -1179,7 +1123,6 @@ function transformKeypointsDirectly(rectType, originalRect, newRect) {
1179
  }
1180
  }
1181
 
1182
- debugLog(`✅ transformKeypointsDirectly: Transformed ${fieldName} keypoints successfully`);
1183
  }
1184
 
1185
  // 🔧 元座標からキーポイントを移動(累積移動防止版)
@@ -1196,12 +1139,10 @@ function moveKeypointsFromOriginal(rectType, totalDeltaX, totalDeltaY) {
1196
  const originalKeypoints = window.poseEditorGlobals.originalKeypoints;
1197
 
1198
  if (!currentPoseData || !currentPoseData.people || !currentPoseData.people[0]) {
1199
- debugLog("❌ moveKeypointsFromOriginal: No current pose data available");
1200
  return;
1201
  }
1202
 
1203
  if (!originalKeypoints || !originalKeypoints.people || !originalKeypoints.people[0]) {
1204
- debugLog("❌ moveKeypointsFromOriginal: No original keypoints available");
1205
  return;
1206
  }
1207
 
@@ -1229,17 +1170,14 @@ function moveKeypointsFromOriginal(rectType, totalDeltaX, totalDeltaY) {
1229
  fieldName = 'hand_right_keypoints_2d';
1230
  break;
1231
  default:
1232
- debugLog(`❌ moveKeypointsFromOriginal: Unknown rectType ${rectType}`);
1233
  return;
1234
  }
1235
 
1236
  if (!targetKeypoints || !Array.isArray(targetKeypoints)) {
1237
- debugLog(`❌ moveKeypointsFromOriginal: No current keypoints for ${fieldName}`);
1238
  return;
1239
  }
1240
 
1241
  if (!originalTargetKeypoints || !Array.isArray(originalTargetKeypoints)) {
1242
- debugLog(`❌ moveKeypointsFromOriginal: No original keypoints for ${fieldName}`);
1243
  return;
1244
  }
1245
 
@@ -1272,7 +1210,6 @@ function moveKeypointsFromOriginal(rectType, totalDeltaX, totalDeltaY) {
1272
  }
1273
  }
1274
 
1275
- debugLog(`🔧 moveKeypointsFromOriginal: ${fieldName}, delta=(${totalDeltaX}, ${totalDeltaY}), normalized: ${isNormalized}`);
1276
 
1277
  // キーポイントを移動(元データから移動量を適用)
1278
  for (let i = 0; i < originalTargetKeypoints.length; i += 3) {
@@ -1311,20 +1248,17 @@ function moveKeypointsFromOriginal(rectType, totalDeltaX, totalDeltaY) {
1311
  }
1312
  }
1313
 
1314
- debugLog(`✅ moveKeypointsFromOriginal: Moved ${fieldName} keypoints successfully`);
1315
  }
1316
 
1317
  // 🔧 矩形移動に合わせてキーポイントを移動(refs互換版)
1318
  function moveKeypointsWithRect(rectType, deltaX, deltaY) {
1319
  const currentPoseData = window.poseEditorGlobals.poseData || poseData;
1320
  if (!currentPoseData || !currentPoseData.people || !currentPoseData.people[0]) {
1321
- debugLog("❌ moveKeypointsWithRect: No current pose data available");
1322
  return;
1323
  }
1324
 
1325
  const person = currentPoseData.people[0];
1326
 
1327
- debugLog(`🔧 キーポイント移動: rectType=${rectType}, delta=(${deltaX}, ${deltaY})`);
1328
 
1329
  // 対応するキーポイントを取得
1330
  let keypoints = null;
@@ -1344,12 +1278,10 @@ function moveKeypointsWithRect(rectType, deltaX, deltaY) {
1344
  fieldName = 'hand_right_keypoints_2d';
1345
  break;
1346
  default:
1347
- debugLog(`❌ moveKeypointsWithRect: Unknown rectType ${rectType}`);
1348
  return;
1349
  }
1350
 
1351
  if (!keypoints || !Array.isArray(keypoints)) {
1352
- debugLog(`❌ moveKeypointsWithRect: No keypoints for ${fieldName}`);
1353
  return;
1354
  }
1355
 
@@ -1366,7 +1298,6 @@ function moveKeypointsWithRect(rectType, deltaX, deltaY) {
1366
  // データを更新
1367
  person[fieldName] = keypoints;
1368
 
1369
- debugLog(`✅ moveKeypointsWithRect: Moved ${fieldName} keypoints successfully`);
1370
  }
1371
 
1372
  // 🚀 全データをエクスポート用フォーマットに同期(refs互換)
@@ -1396,7 +1327,6 @@ function syncAllDataToExportFormat(poseData) {
1396
  }
1397
  }
1398
 
1399
- debugLog(`🔄 All data synced to export format - People: ${poseData.people ? poseData.people.length : 0}`);
1400
  }
1401
 
1402
  // マウス移動処理(refs互換 + 矩形編集対応)
@@ -1427,16 +1357,12 @@ function handleMouseMove(event) {
1427
  const currentPoseData = window.poseEditorGlobals.poseData || poseData;
1428
  if (currentPoseData) {
1429
  // 🫳😊 手と顔データが存在することを確認してから描画
1430
- debugLog(`🎨 Real-time redraw: hands=${!!(currentPoseData.people && currentPoseData.people[0] && (currentPoseData.people[0].hand_left_keypoints_2d || currentPoseData.people[0].hand_right_keypoints_2d))}, faces=${!!currentPoseData.faces}`);
1431
  if (currentPoseData.people && currentPoseData.people[0]) {
1432
  const person = currentPoseData.people[0];
1433
- debugLog(`🎨 People data: left_hand=${!!person.hand_left_keypoints_2d}, right_hand=${!!person.hand_right_keypoints_2d}, face=${!!person.face_keypoints_2d}`);
1434
  }
1435
 
1436
  drawPose(currentPoseData, window.poseEditorGlobals.enableHands, window.poseEditorGlobals.enableFace);
1437
- debugLog("✅ Real-time redraw completed after keypoint update");
1438
  } else {
1439
- debugLog("⚠️ No pose data available for real-time redraw");
1440
  }
1441
  } catch (error) {
1442
  console.error("❌ Error in real-time redraw:", error);
@@ -1456,12 +1382,9 @@ function handleMouseMove(event) {
1456
  const currentPoseData = window.poseEditorGlobals.poseData || poseData;
1457
  if (currentPoseData) {
1458
  // 🫳😊 手と顔データの存在確認
1459
- debugLog(`🎨 Highlighted redraw: hands=${!!(currentPoseData.people && currentPoseData.people[0] && (currentPoseData.people[0].hand_left_keypoints_2d || currentPoseData.people[0].hand_right_keypoints_2d))}, faces=${!!currentPoseData.faces}`);
1460
 
1461
  drawPose(currentPoseData, window.poseEditorGlobals.enableHands, window.poseEditorGlobals.enableFace, draggedKeypoint);
1462
- debugLog("✅ Real-time redraw with highlight completed");
1463
  } else {
1464
- debugLog("⚠️ No pose data available for highlighted redraw");
1465
  }
1466
  } catch (error) {
1467
  console.error("❌ Error in highlighted redraw:", error);
@@ -1505,15 +1428,12 @@ function getControlPointCursor(position) {
1505
  function handleMouseUp(event) {
1506
  if (isDragging) {
1507
  if (window.poseEditorGlobals.draggedRectControl) {
1508
- debugLog(`Control point drag ended: ${window.poseEditorGlobals.draggedRectControl.position}`);
1509
  window.poseEditorGlobals.draggedRectControl = null;
1510
  window.poseEditorGlobals.originalRect = null; // 🔧 元の矩形情報をクリア
1511
  } else if (window.poseEditorGlobals.draggedRect) {
1512
- debugLog(`Rectangle move drag ended: ${window.poseEditorGlobals.draggedRect}`);
1513
  window.poseEditorGlobals.draggedRect = null;
1514
  } else if (window.poseEditorGlobals.draggedDetailKeypoint) {
1515
  // 🎯 詳細モードキーポイントドラッグ終了
1516
- debugLog(`Detail keypoint drag ended: ${window.poseEditorGlobals.draggedDetailKeypoint.type} ${window.poseEditorGlobals.draggedDetailKeypoint.index}`);
1517
  window.poseEditorGlobals.draggedDetailKeypoint = null;
1518
  } else if (draggedKeypoint >= 0) {
1519
  draggedKeypoint = -1;
@@ -1533,7 +1453,6 @@ function updateKeypointPosition(keypointIndex, canvasX, canvasY) {
1533
  const currentPoseData = window.poseEditorGlobals.poseData || poseData;
1534
 
1535
  if (!currentPoseData || !currentPoseData.bodies || !currentPoseData.bodies.candidate) {
1536
- debugLog("No poseData available for keypoint update");
1537
  return;
1538
  }
1539
 
@@ -1583,10 +1502,8 @@ function updateKeypointPosition(keypointIndex, canvasX, canvasY) {
1583
  syncFacesToOriginal(currentPoseData, person.face_keypoints_2d);
1584
  }
1585
 
1586
- debugLog(`🔄 Hands and face data synced after keypoint update`);
1587
  }
1588
 
1589
- debugLog(`🎯 Keypoint ${keypointIndex} updated to (${clampedDataX}, ${clampedDataY})`);
1590
  }
1591
 
1592
  // 🚀 bodies.candidateからpeople.pose_keypoints_2dに同期(refs互換)
@@ -1625,18 +1542,14 @@ function syncBodiesToPeople(poseData) {
1625
  // 🫳😊 既存の手と顔データを保持(ボディ編集時に消失しないように)
1626
  if (!person.hand_left_keypoints_2d && poseData.hands && poseData.hands[0]) {
1627
  person.hand_left_keypoints_2d = poseData.hands[0];
1628
- debugLog(`🫳 Left hand data preserved in people format`);
1629
  }
1630
  if (!person.hand_right_keypoints_2d && poseData.hands && poseData.hands[1]) {
1631
  person.hand_right_keypoints_2d = poseData.hands[1];
1632
- debugLog(`🫳 Right hand data preserved in people format`);
1633
  }
1634
  if (!person.face_keypoints_2d && poseData.faces && poseData.faces.length > 0) {
1635
  person.face_keypoints_2d = poseData.faces[0] || poseData.faces;
1636
- debugLog(`😊 Face data preserved in people format`);
1637
  }
1638
 
1639
- debugLog(`🔄 Bodies synced to people: ${pose_keypoints_2d.length / 3} keypoints, hands preserved: ${!!person.hand_left_keypoints_2d}/${!!person.hand_right_keypoints_2d}, face: ${!!person.face_keypoints_2d}`);
1640
  }
1641
 
1642
  // Gradioにデータ送信(refs互換・強制changeイベント版)
@@ -1654,20 +1567,17 @@ function sendPoseDataToGradio() {
1654
  });
1655
 
1656
  if (!currentPoseData) {
1657
- debugLog("No poseData available for Gradio send");
1658
  return;
1659
  }
1660
 
1661
  // Issue 038: JavaScript側更新フラグチェック(refs issue043準拠)
1662
  if (window.poseEditorGlobals && window.poseEditorGlobals.isUpdating) {
1663
- debugLog("⚠️ JavaScript更新処理中のため、Gradio送信をスキップ");
1664
  return;
1665
  }
1666
 
1667
  // 処理開始フラグを立てる
1668
  if (window.poseEditorGlobals) {
1669
  window.poseEditorGlobals.isUpdating = true;
1670
- debugLog("🔒 JavaScript更新フラグ設定: isUpdating=true");
1671
  }
1672
 
1673
  try {
@@ -1715,29 +1625,22 @@ function sendPoseDataToGradio() {
1715
  // 💖 手と顔データを確実に設定(配列コピーで安全に保持)
1716
  if (leftHandData && leftHandData.length > 0) {
1717
  person.hand_left_keypoints_2d = [...leftHandData]; // 配列コピー
1718
- debugLog(`🫳 Left hand data included (copied): ${leftHandData.length} points`);
1719
  } else {
1720
- debugLog(`⚠️ Left hand data missing or empty`);
1721
  }
1722
 
1723
  if (rightHandData && rightHandData.length > 0) {
1724
  person.hand_right_keypoints_2d = [...rightHandData]; // 配列コピー
1725
- debugLog(`🫳 Right hand data included (copied): ${rightHandData.length} points`);
1726
  } else {
1727
- debugLog(`⚠️ Right hand data missing or empty`);
1728
  }
1729
 
1730
  if (faceData && faceData.length > 0) {
1731
  person.face_keypoints_2d = [...faceData]; // 配列コピー
1732
- debugLog(`😊 Face data included (copied): ${faceData.length} points`);
1733
  } else {
1734
- debugLog(`⚠️ Face data missing or empty`);
1735
  }
1736
 
1737
  canvasData.people = [person];
1738
  } else {
1739
  // bodies.candidateがない場合でも手と顔データがあれば送信
1740
- debugLog("⚠️ No bodies data available, trying to send hands/face only");
1741
 
1742
  const person = {
1743
  "pose_keypoints_2d": [] // 空のボディデータ
@@ -1765,7 +1668,6 @@ function sendPoseDataToGradio() {
1765
  currentPoseData.faces;
1766
  if (hasHandsOrFace) {
1767
  canvasData.people = [person];
1768
- debugLog("🔄 Sending hands/face only data");
1769
  }
1770
  }
1771
 
@@ -1795,7 +1697,6 @@ function sendPoseDataToGradio() {
1795
  if (jsUpdateTextbox) {
1796
  jsUpdateTextbox.value = jsonString;
1797
  jsUpdateTextbox.dispatchEvent(new Event('input', { bubbles: true }));
1798
- debugLog('✅ Pose data sent to Gradio via js_pose_update with timestamp');
1799
  return;
1800
  }
1801
 
@@ -1806,20 +1707,17 @@ function sendPoseDataToGradio() {
1806
  if (currentValue.includes('bodies') || currentValue.includes('candidate') || currentValue.trim() === '') {
1807
  textarea.value = jsonString;
1808
  textarea.dispatchEvent(new Event('input', { bubbles: true }));
1809
- debugLog('⚠️ Pose data sent to Gradio via fallback method with timestamp');
1810
  return;
1811
  }
1812
  }
1813
 
1814
 
1815
  } catch (error) {
1816
- debugLog(`Error sending data to Gradio: ${error.message}`);
1817
  } finally {
1818
  // Issue 038: 処理完了後は必ずフラグを解除(refs issue043準拠)
1819
  if (window.poseEditorGlobals) {
1820
  const oldFlag = window.poseEditorGlobals.isUpdating;
1821
  window.poseEditorGlobals.isUpdating = false;
1822
- debugLog(`🔓 JavaScript更新処理完了 - フラグ解除: ${oldFlag} → ${window.poseEditorGlobals.isUpdating}`);
1823
  }
1824
  }
1825
  }
@@ -1832,7 +1730,9 @@ function isCanvasReady() {
1832
 
1833
  // ポーズ全体の描画(ハイライト対応・設定制御���
1834
  function drawPose(poseData, enableHands = true, enableFace = true, highlightIndex = -1) {
1835
- if (!isCanvasReady() || !poseData) return;
 
 
1836
 
1837
  // 🚀 refs互換:常に最新の編集済みデータを使用
1838
  const currentPoseData = window.poseEditorGlobals.poseData || poseData;
@@ -1883,7 +1783,6 @@ function drawPose(poseData, enableHands = true, enableFace = true, highlightInde
1883
 
1884
  if (handsDataForDrawing && handsDataForDrawing.length >= 2) {
1885
  drawHands(handsDataForDrawing, originalRes, scaleX, scaleY);
1886
- debugLog(`🫳 Hands drawn: left=${handsDataForDrawing[0].length}, right=${handsDataForDrawing[1].length}`);
1887
  }
1888
  } catch (error) {
1889
  console.error("❌ Error drawing hands:", error);
@@ -1891,7 +1790,6 @@ function drawPose(poseData, enableHands = true, enableFace = true, highlightInde
1891
 
1892
  // 🔧 簡易モード:手の矩形描画(編集モード中は再計算しない)
1893
  if (window.poseEditorGlobals.editMode === "簡易モード") {
1894
- debugLog("🔧 Simple mode detected - will draw hand rectangles");
1895
  if (!window.poseEditorGlobals.rectEditModeActive) {
1896
  // 🚀 通常時:編集済みキーポイントから矩形を計算
1897
  if (currentPoseData.people && currentPoseData.people[0]) {
@@ -1912,9 +1810,7 @@ function drawPose(poseData, enableHands = true, enableFace = true, highlightInde
1912
  } else {
1913
  }
1914
  } else if (!enableHands) {
1915
- debugLog("Hand drawing disabled by setting");
1916
  } else {
1917
- debugLog("No hand data available");
1918
  }
1919
 
1920
  // 顔の描画(設定制御・座標変換パラメータ付き・エラー耐性強化)
@@ -1933,7 +1829,6 @@ function drawPose(poseData, enableHands = true, enableFace = true, highlightInde
1933
 
1934
  if (facesDataForDrawing && facesDataForDrawing.length > 0) {
1935
  drawFaces(facesDataForDrawing, originalRes, scaleX, scaleY);
1936
- debugLog(`😊 Face drawn: ${facesDataForDrawing[0].length} keypoints`);
1937
  }
1938
  } catch (error) {
1939
  console.error("❌ Error drawing face:", error);
@@ -1941,7 +1836,6 @@ function drawPose(poseData, enableHands = true, enableFace = true, highlightInde
1941
 
1942
  // 🔧 簡易モード:顔の矩形描画(編集モード中は再計算しない)
1943
  if (window.poseEditorGlobals.editMode === "簡易モード") {
1944
- debugLog("🔧 Simple mode detected - will draw face rectangles");
1945
  if (!window.poseEditorGlobals.rectEditModeActive) {
1946
  // 🚀 通常時:編集済みキーポイントから矩形を計算
1947
  if (currentPoseData.people && currentPoseData.people[0] && currentPoseData.people[0].face_keypoints_2d) {
@@ -1957,9 +1851,7 @@ function drawPose(poseData, enableHands = true, enableFace = true, highlightInde
1957
  } else {
1958
  }
1959
  } else if (!enableFace) {
1960
- debugLog("Face drawing disabled by setting");
1961
  } else {
1962
- debugLog("No face data available");
1963
  }
1964
  }
1965
 
@@ -2007,16 +1899,29 @@ function drawBackground() {
2007
 
2008
  // ボディ描画(ハイライト対応)
2009
  function drawBody(poseData, highlightIndex = -1) {
2010
- if (!poseData.bodies || !poseData.bodies.candidate) {
2011
- console.log(`[ERROR] 💥 No bodies data found!`);
 
 
 
 
 
 
 
 
 
 
 
 
 
2012
  return;
2013
  }
2014
 
2015
  const canvas = window.poseEditorGlobals.canvas;
2016
  const ctx = window.poseEditorGlobals.ctx;
2017
 
2018
- const candidates = poseData.bodies.candidate;
2019
- const subset = poseData.bodies.subset || [];
2020
 
2021
  if (subset.length === 0) {
2022
  // No subset data, using all candidates directly
@@ -2423,7 +2328,6 @@ window.gradioCanvasUpdate = function(pose_json_str) {
2423
  }
2424
 
2425
  if (!isCanvasReady()) {
2426
- debugLog("Canvas not ready, initializing...");
2427
  // console.log(`[DEBUG] isCanvasReady check: canvas=${!!canvas}, ctx=${!!ctx}, isInitialized=${isInitialized}`);
2428
  // console.log(`[DEBUG] window.poseEditorGlobals.canvas=${!!window.poseEditorGlobals.canvas}, window.poseEditorGlobals.ctx=${!!window.poseEditorGlobals.ctx}`);
2429
  initializePoseEditor();
@@ -2445,6 +2349,7 @@ window.gradioCanvasUpdate = function(pose_json_str) {
2445
  window.poseEditorGlobals.enableHands,
2446
  window.poseEditorGlobals.enableFace
2447
  );
 
2448
  }
2449
 
2450
  } catch (error) {
@@ -2461,7 +2366,6 @@ window.gradioCanvasUpdate = function(pose_json_str) {
2461
  // Gradioからのデータ受信用(後方互換性)
2462
  window.updatePoseData = function(data, enableHands = true, enableFace = true) {
2463
  if (!isCanvasReady()) {
2464
- debugLog("Canvas not ready");
2465
  return;
2466
  }
2467
 
@@ -2500,7 +2404,6 @@ window.updateDisplaySettings = function(enableHands, enableFace, editMode) {
2500
  window.poseEditorGlobals.rectEditInfo = null;
2501
  window.poseEditorGlobals.dragStartPos = { x: 0, y: 0 };
2502
 
2503
- debugLog("🔧 All rectangle edit states cleared when switching to detailed mode");
2504
  }
2505
 
2506
  // 🔧 設定更新時は強制的に再描画(レガシー機能)
@@ -2513,7 +2416,6 @@ window.updateDisplaySettings = function(enableHands, enableFace, editMode) {
2513
 
2514
  // Gradioトースト通知のトリガー
2515
  window.showToast = function(type, message) {
2516
- debugLog(`Showing toast: ${type} - ${message}`);
2517
  // Gradioの隠しコンポーネントを使って通知
2518
  if (window.triggerToast) {
2519
  window.triggerToast(type, message);
@@ -2525,12 +2427,10 @@ window.showToast = function(type, message) {
2525
 
2526
  // 🎨 背景画像設定機能(refs互換)
2527
  window.setBackgroundImage = function(imageData) {
2528
- debugLog("🎨 setBackgroundImage called");
2529
 
2530
  if (!imageData) {
2531
  // 背景画像をクリア
2532
  window.poseEditorGlobals.backgroundImage = null;
2533
- debugLog("🎨 Background image cleared");
2534
 
2535
  // 現在のポーズデータがあれば再描画
2536
  const currentPoseData = window.poseEditorGlobals.poseData;
@@ -2543,7 +2443,6 @@ window.setBackgroundImage = function(imageData) {
2543
  const img = new Image();
2544
  img.onload = function() {
2545
  window.poseEditorGlobals.backgroundImage = img;
2546
- debugLog(`🎨 Background image loaded: ${img.width}x${img.height}`);
2547
 
2548
  // 背景画像が設定されたらCanvas再描画
2549
  const currentPoseData = window.poseEditorGlobals.poseData;
@@ -2553,7 +2452,6 @@ window.setBackgroundImage = function(imageData) {
2553
  };
2554
 
2555
  img.onerror = function(e) {
2556
- debugLog("🚨 Failed to load background image");
2557
  };
2558
 
2559
  // Base64データまたはURLから画像を設定
@@ -2590,7 +2488,6 @@ function notifyCanvasStateChange(state) {
2590
 
2591
  // グローバルエラーハンドラー
2592
  window.addEventListener('error', (event) => {
2593
- debugLog(`Global error: ${event.error.message}`);
2594
  if (isCanvasReady()) {
2595
  showCanvasError('エラーが発生しました');
2596
  }
@@ -2598,7 +2495,6 @@ window.addEventListener('error', (event) => {
2598
 
2599
  // Promise rejection ハンドラ
2600
  window.addEventListener('unhandledrejection', (event) => {
2601
- debugLog(`Unhandled promise rejection: ${event.reason}`);
2602
  event.preventDefault();
2603
  if (isCanvasReady()) {
2604
  showCanvasError('非同期処理でエラーが発生しました');
@@ -2610,7 +2506,6 @@ function safeExecute(operation, errorMessage = "操作中にエラーが発生
2610
  try {
2611
  return operation();
2612
  } catch (error) {
2613
- debugLog(`Safe execute error: ${error.message}`);
2614
  if (isCanvasReady()) {
2615
  showCanvasError(errorMessage);
2616
  }
@@ -2628,7 +2523,6 @@ function safeCanvasOperation(operation) {
2628
  function drawHandRectangles(handsData, originalRes, scaleX, scaleY) {
2629
  if (!handsData || handsData.length === 0) return;
2630
 
2631
- debugLog(`🎨 Drawing hand rectangles in mode: ${window.poseEditorGlobals.editMode}`);
2632
 
2633
  const ctx = window.poseEditorGlobals.ctx;
2634
 
@@ -2645,9 +2539,7 @@ function drawHandRectangles(handsData, originalRes, scaleX, scaleY) {
2645
  // 🔧 グローバルに矩形情報保存(編集モード中は既存の矩形を保持)
2646
  if (!window.poseEditorGlobals.currentRects[handType] || !window.poseEditorGlobals.rectEditModeActive) {
2647
  window.poseEditorGlobals.currentRects[handType] = rect;
2648
- debugLog(`🔧 Hand rect calculated for ${handType}: ${JSON.stringify(rect)}`);
2649
  } else {
2650
- debugLog(`🔧 Hand rect preserved for ${handType} (edit mode active)`);
2651
  }
2652
 
2653
  // 描画は現在の矩形を使用(編集中は保存されている矩形)
@@ -2662,7 +2554,6 @@ function drawHandRectangles(handsData, originalRes, scaleX, scaleY) {
2662
  function drawFaceRectangles(facesData, originalRes, scaleX, scaleY) {
2663
  if (!facesData || facesData.length === 0) return;
2664
 
2665
- debugLog(`🎨 Drawing face rectangles in mode: ${window.poseEditorGlobals.editMode}`);
2666
 
2667
  const ctx = window.poseEditorGlobals.ctx;
2668
 
@@ -2676,9 +2567,7 @@ function drawFaceRectangles(facesData, originalRes, scaleX, scaleY) {
2676
  // 🔧 グローバルに矩形情報保存(編集モード中は既存の矩形を保持)
2677
  if (!window.poseEditorGlobals.currentRects.face || !window.poseEditorGlobals.rectEditModeActive) {
2678
  window.poseEditorGlobals.currentRects.face = rect;
2679
- debugLog(`🔧 Face rect calculated: ${JSON.stringify(rect)}`);
2680
  } else {
2681
- debugLog(`🔧 Face rect preserved (edit mode active)`);
2682
  }
2683
 
2684
  // 描画は現在の矩形を使用(編集中は保存されている矩形)
@@ -3056,7 +2945,6 @@ function updateRectControlDrag(mouseX, mouseY) {
3056
 
3057
  // 🔧 rectEditInfo全体初期化(矩形編集モード開始時)
3058
  function initializeRectEditInfo() {
3059
- debugLog('🚀 Initializing rectEditInfo for all rect types');
3060
 
3061
  // 🔧 前回の状態をクリアしてから初期化(連続編集対応)
3062
  window.poseEditorGlobals.rectEditInfo = {};
@@ -3073,7 +2961,6 @@ function initializeRectEditInfo() {
3073
  originalRect: { ...currentRect },
3074
  keypointIndices: getRectKeypointIndices(rectType)
3075
  };
3076
- debugLog(`🔧 rectEditInfo initialized: ${rectType}`, window.poseEditorGlobals.rectEditInfo[rectType]);
3077
  }
3078
  }
3079
 
@@ -3240,11 +3127,7 @@ function moveDetailKeypoints(rectType, deltaX, deltaY) {
3240
  } else if (rectType === 'face' && person.face_keypoints_2d) {
3241
  moveKeypointsArray(person.face_keypoints_2d, deltaX, deltaY, 'face');
3242
  } else {
3243
- debugLog(`❌ No matching detail keypoints for ${rectType}:`, {
3244
- hasLeftHand: !!person.hand_left_keypoints_2d,
3245
- hasRightHand: !!person.hand_right_keypoints_2d,
3246
- hasFace: !!person.face_keypoints_2d
3247
- });
3248
  }
3249
  }
3250
 
@@ -3291,7 +3174,6 @@ function updateRectMoveDrag(mouseX, mouseY) {
3291
  const deltaX = mouseX - startPos.x;
3292
  const deltaY = mouseY - startPos.y;
3293
 
3294
- debugLog(`🔧 矩形移動ドラッグ: ${rectType}, delta=(${deltaX}, ${deltaY})`);
3295
 
3296
  // 矩形の位置を更新(refs方式)
3297
  const newRect = {
@@ -3344,12 +3226,10 @@ function updateKeypointsByRect(rectType, newRect) {
3344
  const originalRect = window.poseEditorGlobals.originalRect;
3345
  if (!originalRect) return;
3346
 
3347
- debugLog(`🔧 Updating ${rectType} keypoints: original=${JSON.stringify(originalRect)}, new=${JSON.stringify(newRect)}`);
3348
 
3349
  // 最小サイズ制限(refs互換)
3350
  const minSize = 20;
3351
  if (newRect.width < minSize || newRect.height < minSize) {
3352
- debugLog(`Rectangle too small, skipping update`);
3353
  return;
3354
  }
3355
 
@@ -3390,7 +3270,6 @@ function updateKeypointsByRect(rectType, newRect) {
3390
  }
3391
 
3392
  if (!targetKeypoints) {
3393
- debugLog(`No keypoints found for ${rectType}`);
3394
  return;
3395
  }
3396
 
@@ -3539,7 +3418,6 @@ function moveKeypointsByRect(rectType, deltaX, deltaY) {
3539
  }
3540
 
3541
  if (!targetKeypoints) {
3542
- debugLog(`No keypoints found for ${rectType}`);
3543
  return;
3544
  }
3545
 
 
1
  // Canvas操作用JavaScript for dwpose-editor
2
 
3
+ // グローバル変数
4
  window.poseEditorGlobals = {
5
  canvas: null,
6
  ctx: null,
 
59
  let draggedKeypoint = -1;
60
  let dragOffset = { x: 0, y: 0 };
61
 
62
+ // デバッグログ機能は削除済み
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
63
 
64
+ // Canvas初期化関数
65
  function initializePoseEditor() {
 
66
 
67
  canvas = document.getElementById('pose_canvas');
68
  if (!canvas) {
 
69
  setTimeout(initializePoseEditor, 100);
70
  return;
71
  }
72
 
73
  ctx = canvas.getContext('2d');
74
  if (!ctx) {
 
75
  return;
76
  }
77
 
 
94
  canvas.style.cursor = 'default';
95
 
96
  isInitialized = true;
 
97
  notifyCanvasStateChange('initialized');
98
 
99
  // ドラッグイベントを設定(refs互換)
 
151
 
152
  // ドラッグイベント設定(refs互換)
153
  function setupDragEvents() {
 
154
 
155
  if (!canvas) {
 
156
  return;
157
  }
158
 
 
166
  canvas.addEventListener('click', function(event) {
167
  });
168
 
 
169
  }
170
 
171
  // 🔧 Gradioチェックボックス監視設定
172
  function setupGradioCheckboxListeners() {
173
  // チェックボックスとラジオボタン要素を探す(少し待ってから)
174
  setTimeout(() => {
 
175
 
176
  // チェックボックス監視
177
  const allCheckboxes = document.querySelectorAll('input[type="checkbox"]');
 
192
 
193
  // ラジオボタン監視(編集モード)
194
  const allRadios = document.querySelectorAll('input[type="radio"]');
 
195
 
196
  allRadios.forEach((radio, index) => {
197
  // 親要素のテキストから簡易モード・詳細モードを特定
198
  const parentText = radio.parentElement?.textContent?.trim() || '';
199
  const grandParentText = radio.parentElement?.parentElement?.textContent?.trim() || '';
200
 
 
201
 
202
  if (parentText.includes('簡易モード') || parentText.includes('詳細モード') ||
203
  grandParentText.includes('編集モード')) {
 
281
  }
282
 
283
  if (!currentPoseData.bodies) {
 
284
  return -1;
285
  }
286
 
287
  if (!currentPoseData.bodies.candidate) {
 
288
  return -1;
289
  }
290
 
 
426
  // 詳細モード用:最寄りのキーポイント検索(手・顔の個別キーポイント対応)
427
  function findNearestKeypointInDetailMode(clickX, clickY) {
428
  if (!poseData || !poseData.people || poseData.people.length === 0) {
 
429
  return null;
430
  }
431
 
 
593
  const currentRect = window.poseEditorGlobals.currentRects[rectType];
594
  if (currentRect) {
595
  window.poseEditorGlobals.originalRect = { ...currentRect };
 
596
  }
597
 
598
  // コントロールポイントドラッグ(リサイズ)
 
608
  window.poseEditorGlobals.draggedRect = rectType;
609
  window.poseEditorGlobals.dragStartPos = { x: mousePos.x, y: mousePos.y };
610
  isDragging = true;
 
611
  return;
612
  }
613
 
614
  // 他の矩形クリック → 切り替え
615
  if (rectType && rectType !== window.poseEditorGlobals.rectEditMode) {
616
  window.poseEditorGlobals.rectEditMode = rectType;
 
617
  // 再描画で新しい矩形のコントロールポイントを表示
618
  const currentPoseData = window.poseEditorGlobals.poseData || poseData;
619
  if (currentPoseData) {
 
645
  window.poseEditorGlobals.draggedRect = null;
646
  window.poseEditorGlobals.dragStartPos = { x: 0, y: 0 };
647
 
 
648
 
649
  // 🚀 矩形位置を保持したまま再描画(再計算させない)
650
  if (currentPoseData) {
 
659
  if (rectType) {
660
  // 矩形モード切り替え時の処理
661
  if (window.poseEditorGlobals.rectEditMode !== rectType) {
 
662
  window.poseEditorGlobals.rectEditMode = rectType;
663
  }
664
 
665
  window.poseEditorGlobals.rectEditModeActive = true;
 
666
 
667
  // 🔧 rectEditInfo全体を初期化(全矩形タイプ対応)
668
  initializeRectEditInfo();
 
718
  }
719
  }
720
 
 
721
  canvas.style.cursor = 'grabbing';
722
  } else {
 
723
  }
724
  }
725
  }
 
730
  const currentPoseData = window.poseEditorGlobals.poseData || poseData;
731
 
732
  if (!currentPoseData) {
 
733
  return;
734
  }
735
 
 
761
  handData[arrayIndex] = dataX;
762
  handData[arrayIndex + 1] = dataY;
763
  // 信頼度は維持
 
764
 
765
  // 元のhandsデータも同期更新
766
  syncHandsToOriginal(currentPoseData, 'left', handData);
 
788
  handData[arrayIndex] = dataX;
789
  handData[arrayIndex + 1] = dataY;
790
  // 信頼度は維持
 
791
 
792
  // 元のhandsデータも同期更新
793
  syncHandsToOriginal(currentPoseData, 'right', handData);
 
815
  faceData[arrayIndex] = dataX;
816
  faceData[arrayIndex + 1] = dataY;
817
  // 信頼度は維持
 
818
 
819
  // 🚀 元のfacesデータも同期更新
820
  syncFacesToOriginal(currentPoseData, faceData);
 
837
  const handIndex = handType === 'left' ? 0 : 1;
838
  if (handIndex < poseData.hands.length) {
839
  poseData.hands[handIndex] = [...handData];
 
840
  }
841
  }
842
 
 
846
 
847
  if (poseData.faces.length > 0) {
848
  poseData.faces[0] = [...faceData];
 
849
  }
850
  }
851
 
 
1001
  // 11. 矩形情報を更新
1002
  control.rect = newRect;
1003
 
 
1004
  }
1005
 
1006
  // 🔧 直接矩形変換関数(シンプル版)
 
1009
  const originalKeypoints = window.poseEditorGlobals.originalKeypoints;
1010
 
1011
  if (!currentPoseData || !currentPoseData.people || !currentPoseData.people[0]) {
 
1012
  return;
1013
  }
1014
 
1015
  if (!originalKeypoints || !originalKeypoints.people || !originalKeypoints.people[0]) {
 
1016
  return;
1017
  }
1018
 
 
1040
  fieldName = 'hand_right_keypoints_2d';
1041
  break;
1042
  default:
 
1043
  return;
1044
  }
1045
 
1046
  if (!targetKeypoints || !Array.isArray(targetKeypoints)) {
 
 
1047
  return;
1048
  }
1049
 
1050
  if (!originalTargetKeypoints || !Array.isArray(originalTargetKeypoints)) {
 
 
1051
  return;
1052
  }
1053
 
 
1054
 
1055
  // 座標変換設定
1056
  const canvasWidth = window.poseEditorGlobals.canvas ? window.poseEditorGlobals.canvas.width : 512;
 
1081
  }
1082
  }
1083
 
 
1084
 
1085
  // キーポイントを一括変換(元データから毎回変換)
1086
  for (let i = 0; i < originalTargetKeypoints.length; i += 3) {
 
1123
  }
1124
  }
1125
 
 
1126
  }
1127
 
1128
  // 🔧 元座標からキーポイントを移動(累積移動防止版)
 
1139
  const originalKeypoints = window.poseEditorGlobals.originalKeypoints;
1140
 
1141
  if (!currentPoseData || !currentPoseData.people || !currentPoseData.people[0]) {
 
1142
  return;
1143
  }
1144
 
1145
  if (!originalKeypoints || !originalKeypoints.people || !originalKeypoints.people[0]) {
 
1146
  return;
1147
  }
1148
 
 
1170
  fieldName = 'hand_right_keypoints_2d';
1171
  break;
1172
  default:
 
1173
  return;
1174
  }
1175
 
1176
  if (!targetKeypoints || !Array.isArray(targetKeypoints)) {
 
1177
  return;
1178
  }
1179
 
1180
  if (!originalTargetKeypoints || !Array.isArray(originalTargetKeypoints)) {
 
1181
  return;
1182
  }
1183
 
 
1210
  }
1211
  }
1212
 
 
1213
 
1214
  // キーポイントを移動(元データから移動量を適用)
1215
  for (let i = 0; i < originalTargetKeypoints.length; i += 3) {
 
1248
  }
1249
  }
1250
 
 
1251
  }
1252
 
1253
  // 🔧 矩形移動に合わせてキーポイントを移動(refs互換版)
1254
  function moveKeypointsWithRect(rectType, deltaX, deltaY) {
1255
  const currentPoseData = window.poseEditorGlobals.poseData || poseData;
1256
  if (!currentPoseData || !currentPoseData.people || !currentPoseData.people[0]) {
 
1257
  return;
1258
  }
1259
 
1260
  const person = currentPoseData.people[0];
1261
 
 
1262
 
1263
  // 対応するキーポイントを取得
1264
  let keypoints = null;
 
1278
  fieldName = 'hand_right_keypoints_2d';
1279
  break;
1280
  default:
 
1281
  return;
1282
  }
1283
 
1284
  if (!keypoints || !Array.isArray(keypoints)) {
 
1285
  return;
1286
  }
1287
 
 
1298
  // データを更新
1299
  person[fieldName] = keypoints;
1300
 
 
1301
  }
1302
 
1303
  // 🚀 全データをエクスポート用フォーマットに同期(refs互換)
 
1327
  }
1328
  }
1329
 
 
1330
  }
1331
 
1332
  // マウス移動処理(refs互換 + 矩形編集対応)
 
1357
  const currentPoseData = window.poseEditorGlobals.poseData || poseData;
1358
  if (currentPoseData) {
1359
  // 🫳😊 手と顔データが存在することを確認してから描画
 
1360
  if (currentPoseData.people && currentPoseData.people[0]) {
1361
  const person = currentPoseData.people[0];
 
1362
  }
1363
 
1364
  drawPose(currentPoseData, window.poseEditorGlobals.enableHands, window.poseEditorGlobals.enableFace);
 
1365
  } else {
 
1366
  }
1367
  } catch (error) {
1368
  console.error("❌ Error in real-time redraw:", error);
 
1382
  const currentPoseData = window.poseEditorGlobals.poseData || poseData;
1383
  if (currentPoseData) {
1384
  // 🫳😊 手と顔データの存在確認
 
1385
 
1386
  drawPose(currentPoseData, window.poseEditorGlobals.enableHands, window.poseEditorGlobals.enableFace, draggedKeypoint);
 
1387
  } else {
 
1388
  }
1389
  } catch (error) {
1390
  console.error("❌ Error in highlighted redraw:", error);
 
1428
  function handleMouseUp(event) {
1429
  if (isDragging) {
1430
  if (window.poseEditorGlobals.draggedRectControl) {
 
1431
  window.poseEditorGlobals.draggedRectControl = null;
1432
  window.poseEditorGlobals.originalRect = null; // 🔧 元の矩形情報をクリア
1433
  } else if (window.poseEditorGlobals.draggedRect) {
 
1434
  window.poseEditorGlobals.draggedRect = null;
1435
  } else if (window.poseEditorGlobals.draggedDetailKeypoint) {
1436
  // 🎯 詳細モードキーポイントドラッグ終了
 
1437
  window.poseEditorGlobals.draggedDetailKeypoint = null;
1438
  } else if (draggedKeypoint >= 0) {
1439
  draggedKeypoint = -1;
 
1453
  const currentPoseData = window.poseEditorGlobals.poseData || poseData;
1454
 
1455
  if (!currentPoseData || !currentPoseData.bodies || !currentPoseData.bodies.candidate) {
 
1456
  return;
1457
  }
1458
 
 
1502
  syncFacesToOriginal(currentPoseData, person.face_keypoints_2d);
1503
  }
1504
 
 
1505
  }
1506
 
 
1507
  }
1508
 
1509
  // 🚀 bodies.candidateからpeople.pose_keypoints_2dに同期(refs互換)
 
1542
  // 🫳😊 既存の手と顔データを保持(ボディ編集時に消失しないように)
1543
  if (!person.hand_left_keypoints_2d && poseData.hands && poseData.hands[0]) {
1544
  person.hand_left_keypoints_2d = poseData.hands[0];
 
1545
  }
1546
  if (!person.hand_right_keypoints_2d && poseData.hands && poseData.hands[1]) {
1547
  person.hand_right_keypoints_2d = poseData.hands[1];
 
1548
  }
1549
  if (!person.face_keypoints_2d && poseData.faces && poseData.faces.length > 0) {
1550
  person.face_keypoints_2d = poseData.faces[0] || poseData.faces;
 
1551
  }
1552
 
 
1553
  }
1554
 
1555
  // Gradioにデータ送信(refs互換・強制changeイベント版)
 
1567
  });
1568
 
1569
  if (!currentPoseData) {
 
1570
  return;
1571
  }
1572
 
1573
  // Issue 038: JavaScript側更新フラグチェック(refs issue043準拠)
1574
  if (window.poseEditorGlobals && window.poseEditorGlobals.isUpdating) {
 
1575
  return;
1576
  }
1577
 
1578
  // 処理開始フラグを立てる
1579
  if (window.poseEditorGlobals) {
1580
  window.poseEditorGlobals.isUpdating = true;
 
1581
  }
1582
 
1583
  try {
 
1625
  // 💖 手と顔データを確実に設定(配列コピーで安全に保持)
1626
  if (leftHandData && leftHandData.length > 0) {
1627
  person.hand_left_keypoints_2d = [...leftHandData]; // 配列コピー
 
1628
  } else {
 
1629
  }
1630
 
1631
  if (rightHandData && rightHandData.length > 0) {
1632
  person.hand_right_keypoints_2d = [...rightHandData]; // 配列コピー
 
1633
  } else {
 
1634
  }
1635
 
1636
  if (faceData && faceData.length > 0) {
1637
  person.face_keypoints_2d = [...faceData]; // 配列コピー
 
1638
  } else {
 
1639
  }
1640
 
1641
  canvasData.people = [person];
1642
  } else {
1643
  // bodies.candidateがない場合でも手と顔データがあれば送信
 
1644
 
1645
  const person = {
1646
  "pose_keypoints_2d": [] // 空のボディデータ
 
1668
  currentPoseData.faces;
1669
  if (hasHandsOrFace) {
1670
  canvasData.people = [person];
 
1671
  }
1672
  }
1673
 
 
1697
  if (jsUpdateTextbox) {
1698
  jsUpdateTextbox.value = jsonString;
1699
  jsUpdateTextbox.dispatchEvent(new Event('input', { bubbles: true }));
 
1700
  return;
1701
  }
1702
 
 
1707
  if (currentValue.includes('bodies') || currentValue.includes('candidate') || currentValue.trim() === '') {
1708
  textarea.value = jsonString;
1709
  textarea.dispatchEvent(new Event('input', { bubbles: true }));
 
1710
  return;
1711
  }
1712
  }
1713
 
1714
 
1715
  } catch (error) {
 
1716
  } finally {
1717
  // Issue 038: 処理完了後は必ずフラグを解除(refs issue043準拠)
1718
  if (window.poseEditorGlobals) {
1719
  const oldFlag = window.poseEditorGlobals.isUpdating;
1720
  window.poseEditorGlobals.isUpdating = false;
 
1721
  }
1722
  }
1723
  }
 
1730
 
1731
  // ポーズ全体の描画(ハイライト対応・設定制御���
1732
  function drawPose(poseData, enableHands = true, enableFace = true, highlightIndex = -1) {
1733
+ if (!isCanvasReady() || !poseData) {
1734
+ return;
1735
+ }
1736
 
1737
  // 🚀 refs互換:常に最新の編集済みデータを使用
1738
  const currentPoseData = window.poseEditorGlobals.poseData || poseData;
 
1783
 
1784
  if (handsDataForDrawing && handsDataForDrawing.length >= 2) {
1785
  drawHands(handsDataForDrawing, originalRes, scaleX, scaleY);
 
1786
  }
1787
  } catch (error) {
1788
  console.error("❌ Error drawing hands:", error);
 
1790
 
1791
  // 🔧 簡易モード:手の矩形描画(編集モード中は再計算しない)
1792
  if (window.poseEditorGlobals.editMode === "簡易モード") {
 
1793
  if (!window.poseEditorGlobals.rectEditModeActive) {
1794
  // 🚀 通常時:編集済みキーポイントから矩形を計算
1795
  if (currentPoseData.people && currentPoseData.people[0]) {
 
1810
  } else {
1811
  }
1812
  } else if (!enableHands) {
 
1813
  } else {
 
1814
  }
1815
 
1816
  // 顔の描画(設定制御・座標変換パラメータ付き・エラー耐性強化)
 
1829
 
1830
  if (facesDataForDrawing && facesDataForDrawing.length > 0) {
1831
  drawFaces(facesDataForDrawing, originalRes, scaleX, scaleY);
 
1832
  }
1833
  } catch (error) {
1834
  console.error("❌ Error drawing face:", error);
 
1836
 
1837
  // 🔧 簡易モード:顔の矩形描画(編集モード中は再計算しない)
1838
  if (window.poseEditorGlobals.editMode === "簡易モード") {
 
1839
  if (!window.poseEditorGlobals.rectEditModeActive) {
1840
  // 🚀 通常時:編集済みキーポイントから矩形を計算
1841
  if (currentPoseData.people && currentPoseData.people[0] && currentPoseData.people[0].face_keypoints_2d) {
 
1851
  } else {
1852
  }
1853
  } else if (!enableFace) {
 
1854
  } else {
 
1855
  }
1856
  }
1857
 
 
1899
 
1900
  // ボディ描画(ハイライト対応)
1901
  function drawBody(poseData, highlightIndex = -1) {
1902
+
1903
+ // people形式からbodies形式への変換を試行
1904
+ let candidatesData = null;
1905
+ if (poseData.bodies && poseData.bodies.candidate) {
1906
+ candidatesData = poseData.bodies.candidate;
1907
+ } else if (poseData.people && poseData.people[0] && poseData.people[0].pose_keypoints_2d) {
1908
+ // people形式からcandidate形式に変換
1909
+ const pose_keypoints_2d = poseData.people[0].pose_keypoints_2d;
1910
+ candidatesData = [];
1911
+ for (let i = 0; i < pose_keypoints_2d.length; i += 3) {
1912
+ if (i + 2 < pose_keypoints_2d.length) {
1913
+ candidatesData.push([pose_keypoints_2d[i], pose_keypoints_2d[i+1], pose_keypoints_2d[i+2]]);
1914
+ }
1915
+ }
1916
+ } else {
1917
  return;
1918
  }
1919
 
1920
  const canvas = window.poseEditorGlobals.canvas;
1921
  const ctx = window.poseEditorGlobals.ctx;
1922
 
1923
+ const candidates = candidatesData;
1924
+ const subset = (poseData.bodies && poseData.bodies.subset) || [];
1925
 
1926
  if (subset.length === 0) {
1927
  // No subset data, using all candidates directly
 
2328
  }
2329
 
2330
  if (!isCanvasReady()) {
 
2331
  // console.log(`[DEBUG] isCanvasReady check: canvas=${!!canvas}, ctx=${!!ctx}, isInitialized=${isInitialized}`);
2332
  // console.log(`[DEBUG] window.poseEditorGlobals.canvas=${!!window.poseEditorGlobals.canvas}, window.poseEditorGlobals.ctx=${!!window.poseEditorGlobals.ctx}`);
2333
  initializePoseEditor();
 
2349
  window.poseEditorGlobals.enableHands,
2350
  window.poseEditorGlobals.enableFace
2351
  );
2352
+ } else {
2353
  }
2354
 
2355
  } catch (error) {
 
2366
  // Gradioからのデータ受信用(後方互換性)
2367
  window.updatePoseData = function(data, enableHands = true, enableFace = true) {
2368
  if (!isCanvasReady()) {
 
2369
  return;
2370
  }
2371
 
 
2404
  window.poseEditorGlobals.rectEditInfo = null;
2405
  window.poseEditorGlobals.dragStartPos = { x: 0, y: 0 };
2406
 
 
2407
  }
2408
 
2409
  // 🔧 設定更新時は強制的に再描画(レガシー機能)
 
2416
 
2417
  // Gradioトースト通知のトリガー
2418
  window.showToast = function(type, message) {
 
2419
  // Gradioの隠しコンポーネントを使って通知
2420
  if (window.triggerToast) {
2421
  window.triggerToast(type, message);
 
2427
 
2428
  // 🎨 背景画像設定機能(refs互換)
2429
  window.setBackgroundImage = function(imageData) {
 
2430
 
2431
  if (!imageData) {
2432
  // 背景画像をクリア
2433
  window.poseEditorGlobals.backgroundImage = null;
 
2434
 
2435
  // 現在のポーズデータがあれば再描画
2436
  const currentPoseData = window.poseEditorGlobals.poseData;
 
2443
  const img = new Image();
2444
  img.onload = function() {
2445
  window.poseEditorGlobals.backgroundImage = img;
 
2446
 
2447
  // 背景画像が設定されたらCanvas再描画
2448
  const currentPoseData = window.poseEditorGlobals.poseData;
 
2452
  };
2453
 
2454
  img.onerror = function(e) {
 
2455
  };
2456
 
2457
  // Base64データまたはURLから画像を設定
 
2488
 
2489
  // グローバルエラーハンドラー
2490
  window.addEventListener('error', (event) => {
 
2491
  if (isCanvasReady()) {
2492
  showCanvasError('エラーが発生しました');
2493
  }
 
2495
 
2496
  // Promise rejection ハンドラ
2497
  window.addEventListener('unhandledrejection', (event) => {
 
2498
  event.preventDefault();
2499
  if (isCanvasReady()) {
2500
  showCanvasError('非同期処理でエラーが発生しました');
 
2506
  try {
2507
  return operation();
2508
  } catch (error) {
 
2509
  if (isCanvasReady()) {
2510
  showCanvasError(errorMessage);
2511
  }
 
2523
  function drawHandRectangles(handsData, originalRes, scaleX, scaleY) {
2524
  if (!handsData || handsData.length === 0) return;
2525
 
 
2526
 
2527
  const ctx = window.poseEditorGlobals.ctx;
2528
 
 
2539
  // 🔧 グローバルに矩形情報保存(編集モード中は既存の矩形を保持)
2540
  if (!window.poseEditorGlobals.currentRects[handType] || !window.poseEditorGlobals.rectEditModeActive) {
2541
  window.poseEditorGlobals.currentRects[handType] = rect;
 
2542
  } else {
 
2543
  }
2544
 
2545
  // 描画は現在の矩形を使用(編集中は保存されている矩形)
 
2554
  function drawFaceRectangles(facesData, originalRes, scaleX, scaleY) {
2555
  if (!facesData || facesData.length === 0) return;
2556
 
 
2557
 
2558
  const ctx = window.poseEditorGlobals.ctx;
2559
 
 
2567
  // 🔧 グローバルに矩形情報保存(編集モード中は既存の矩形を保持)
2568
  if (!window.poseEditorGlobals.currentRects.face || !window.poseEditorGlobals.rectEditModeActive) {
2569
  window.poseEditorGlobals.currentRects.face = rect;
 
2570
  } else {
 
2571
  }
2572
 
2573
  // 描画は現在の矩形を使用(編集中は保存されている矩形)
 
2945
 
2946
  // 🔧 rectEditInfo全体初期化(矩形編集モード開始時)
2947
  function initializeRectEditInfo() {
 
2948
 
2949
  // 🔧 前回の状態をクリアしてから初期化(連続編集対応)
2950
  window.poseEditorGlobals.rectEditInfo = {};
 
2961
  originalRect: { ...currentRect },
2962
  keypointIndices: getRectKeypointIndices(rectType)
2963
  };
 
2964
  }
2965
  }
2966
 
 
3127
  } else if (rectType === 'face' && person.face_keypoints_2d) {
3128
  moveKeypointsArray(person.face_keypoints_2d, deltaX, deltaY, 'face');
3129
  } else {
3130
+ // No matching detail keypoints found
 
 
 
 
3131
  }
3132
  }
3133
 
 
3174
  const deltaX = mouseX - startPos.x;
3175
  const deltaY = mouseY - startPos.y;
3176
 
 
3177
 
3178
  // 矩形の位置を更新(refs方式)
3179
  const newRect = {
 
3226
  const originalRect = window.poseEditorGlobals.originalRect;
3227
  if (!originalRect) return;
3228
 
 
3229
 
3230
  // 最小サイズ制限(refs互換)
3231
  const minSize = 20;
3232
  if (newRect.width < minSize || newRect.height < minSize) {
 
3233
  return;
3234
  }
3235
 
 
3270
  }
3271
 
3272
  if (!targetKeypoints) {
 
3273
  return;
3274
  }
3275
 
 
3418
  }
3419
 
3420
  if (!targetKeypoints) {
 
3421
  return;
3422
  }
3423
 
utils/notifications.py CHANGED
@@ -8,25 +8,29 @@ NOTIFICATION_SETTINGS = {
8
  'show_warnings': True,
9
  'show_errors': True,
10
  'auto_dismiss': True,
11
- 'dismiss_timeout': 1500 # 1.5秒(エラー以外)
 
12
  }
13
 
14
  def notify_success(message):
15
  """成功通知"""
16
  if NOTIFICATION_SETTINGS['show_success']:
17
- return gr.Info(message)
 
18
  return None
19
 
20
  def notify_warning(message):
21
  """警告通知"""
22
  if NOTIFICATION_SETTINGS['show_warnings']:
23
- return gr.Warning(message)
 
24
  return None
25
 
26
  def notify_error(message):
27
  """エラー通知"""
28
  if NOTIFICATION_SETTINGS['show_errors']:
29
- return gr.Error(message)
 
30
  return None
31
 
32
  def notify_progress(message, progress=None):
 
8
  'show_warnings': True,
9
  'show_errors': True,
10
  'auto_dismiss': True,
11
+ 'info_duration': 3.0, # Info系:3秒
12
+ 'error_duration': 5.0 # Error系:5秒
13
  }
14
 
15
  def notify_success(message):
16
  """成功通知"""
17
  if NOTIFICATION_SETTINGS['show_success']:
18
+ duration = NOTIFICATION_SETTINGS['info_duration'] # 2秒
19
+ return gr.Info(message, duration=duration)
20
  return None
21
 
22
  def notify_warning(message):
23
  """警告通知"""
24
  if NOTIFICATION_SETTINGS['show_warnings']:
25
+ duration = NOTIFICATION_SETTINGS['info_duration'] # 2秒
26
+ return gr.Warning(message, duration=duration)
27
  return None
28
 
29
  def notify_error(message):
30
  """エラー通知"""
31
  if NOTIFICATION_SETTINGS['show_errors']:
32
+ duration = NOTIFICATION_SETTINGS['error_duration'] # 5秒
33
+ return gr.Error(message, duration=duration)
34
  return None
35
 
36
  def notify_progress(message, progress=None):