grmchn commited on
Commit
521c583
·
1 Parent(s): 1aafb6a

fix(044): hand rect edit stability

Browse files

- Use keypoint BBox as transform refRect (canvas coords)
- Unify resolution source (metadata.resolution → resolution → 512)
- Clamp relative ratios to 0–1 to avoid overshoot/flip
- Convert rect move delta from canvas→data coords
- Remove wrong ‘hands are canvas coords’ assumption
- Gate debug logs via window.poseEditorDebug

Hands’ resize now follows direction/amount like face ✅🖐️

Files changed (2) hide show
  1. .gitignore +1 -1
  2. static/pose_editor.js +282 -48
.gitignore CHANGED
@@ -115,4 +115,4 @@ development_tests/
115
  issues/
116
  CLAUDE.md
117
  .claude/
118
-
 
115
  issues/
116
  CLAUDE.md
117
  .claude/
118
+ AGENTS.md
static/pose_editor.js CHANGED
@@ -1,5 +1,12 @@
1
  // Canvas操作用JavaScript for dwpose-editor
2
 
 
 
 
 
 
 
 
3
  // グローバル変数
4
  window.poseEditorGlobals = {
5
  canvas: null,
@@ -593,6 +600,12 @@ function handleMouseDown(event) {
593
  const currentRect = window.poseEditorGlobals.currentRects[rectType];
594
  if (currentRect) {
595
  window.poseEditorGlobals.originalRect = { ...currentRect };
 
 
 
 
 
 
596
  }
597
 
598
  // コントロールポイントドラッグ(リサイズ)
@@ -1056,11 +1069,13 @@ function transformKeypointsDirectly(rectType, originalRect, newRect) {
1056
  const canvasWidth = window.poseEditorGlobals.canvas ? window.poseEditorGlobals.canvas.width : 512;
1057
  const canvasHeight = window.poseEditorGlobals.canvas ? window.poseEditorGlobals.canvas.height : 512;
1058
 
1059
- let dataResolutionWidth = canvasWidth;
1060
- let dataResolutionHeight = canvasHeight;
1061
-
1062
- // 解像度情報の取得
1063
- if (currentPoseData.resolution && Array.isArray(currentPoseData.resolution) && currentPoseData.resolution.length >= 2) {
 
 
1064
  dataResolutionWidth = currentPoseData.resolution[0];
1065
  dataResolutionHeight = currentPoseData.resolution[1];
1066
  }
@@ -1068,21 +1083,87 @@ function transformKeypointsDirectly(rectType, originalRect, newRect) {
1068
  const coordScaleX = canvasWidth / dataResolutionWidth;
1069
  const coordScaleY = canvasHeight / dataResolutionHeight;
1070
 
1071
- // 正規化座標かピクセル座標かを判定
1072
  let isNormalized = false;
1073
- if (targetKeypoints.length > 0) {
1074
- for (let i = 0; i < targetKeypoints.length; i += 3) {
1075
- if (i + 2 < targetKeypoints.length && targetKeypoints[i + 2] > 0) {
1076
- const x = targetKeypoints[i];
1077
- const y = targetKeypoints[i + 1];
1078
- isNormalized = (x >= 0 && x <= 1 && y >= 0 && y <= 1);
1079
- break;
 
 
 
 
 
 
 
 
 
 
 
 
 
1080
  }
1081
  }
 
 
 
 
1082
  }
1083
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1084
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1085
  // キーポイントを一括変換(元データから毎回変換)
 
1086
  for (let i = 0; i < originalTargetKeypoints.length; i += 3) {
1087
  if (i + 2 < originalTargetKeypoints.length && i + 2 < targetKeypoints.length) {
1088
  const confidence = originalTargetKeypoints[i + 2];
@@ -1091,38 +1172,89 @@ function transformKeypointsDirectly(rectType, originalRect, newRect) {
1091
  let x = originalTargetKeypoints[i];
1092
  let y = originalTargetKeypoints[i + 1];
1093
 
1094
- // データ座標→Canvas座標
1095
  let canvasX, canvasY;
1096
  if (isNormalized) {
1097
  canvasX = (x * dataResolutionWidth) * coordScaleX;
1098
  canvasY = (y * dataResolutionHeight) * coordScaleY;
 
 
 
 
1099
  } else {
1100
  canvasX = x * coordScaleX;
1101
  canvasY = y * coordScaleY;
1102
  }
1103
 
1104
- // 元矩形内での相対位置を計算
1105
- const relativeX = (canvasX - originalRect.x) / originalRect.width;
1106
- const relativeY = (canvasY - originalRect.y) / originalRect.height;
1107
-
 
 
 
1108
  // 新矩形での新しい位置を計算
1109
  const newCanvasX = newRect.x + (relativeX * newRect.width);
1110
  const newCanvasY = newRect.y + (relativeY * newRect.height);
1111
 
1112
- // Canvas座標→データ座標に戻す
 
1113
  if (isNormalized) {
1114
  const dataX = newCanvasX / coordScaleX;
1115
  const dataY = newCanvasY / coordScaleY;
1116
- targetKeypoints[i] = dataX / dataResolutionWidth;
1117
- targetKeypoints[i + 1] = dataY / dataResolutionHeight;
 
 
1118
  } else {
1119
- targetKeypoints[i] = newCanvasX / coordScaleX;
1120
- targetKeypoints[i + 1] = newCanvasY / coordScaleY;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1121
  }
1122
  }
1123
  }
1124
  }
1125
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1126
  }
1127
 
1128
  // 🔧 元座標からキーポイントを移動(累積移動防止版)
@@ -1285,13 +1417,32 @@ function moveKeypointsWithRect(rectType, deltaX, deltaY) {
1285
  return;
1286
  }
1287
 
1288
- // すべてのキーポイントを移動(refs方式)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1289
  for (let i = 0; i < keypoints.length; i += 3) {
1290
  const confidence = keypoints[i + 2];
1291
 
1292
  if (confidence > 0.1) { // 有効なキーポイントのみ移動
1293
- keypoints[i] += deltaX; // X座標
1294
- keypoints[i + 1] += deltaY; // Y座標
1295
  }
1296
  }
1297
 
@@ -1554,10 +1705,10 @@ function syncBodiesToPeople(poseData) {
1554
 
1555
  // Gradioにデータ送信(refs互換・強制changeイベント版)
1556
  function sendPoseDataToGradio() {
1557
- console.log('🔍 [sendPoseDataToGradio] Called');
1558
  // グローバルposeDataを参照
1559
  const currentPoseData = window.poseEditorGlobals.poseData || poseData;
1560
- console.log('🔍 [sendPoseDataToGradio] currentPoseData state:', {
1561
  exists: !!currentPoseData,
1562
  hasPeople: !!currentPoseData?.people,
1563
  peopleCount: currentPoseData?.people?.length || 0,
@@ -1677,7 +1828,7 @@ function sendPoseDataToGradio() {
1677
  }
1678
 
1679
  // 送信前の最終データ確認
1680
- console.log('🔍 [sendPoseDataToGradio] Final canvasData being sent:', {
1681
  hasPeople: !!canvasData.people,
1682
  peopleCount: canvasData.people?.length || 0,
1683
  hasHandLeft: !!canvasData.people?.[0]?.hand_left_keypoints_2d,
@@ -1689,7 +1840,7 @@ function sendPoseDataToGradio() {
1689
  });
1690
 
1691
  const jsonString = JSON.stringify(canvasData);
1692
- console.log('🎯 [sendPoseDataToGradio] People形式でGradioに送信:', canvasData.people.length, 'people');
1693
 
1694
  // 専用の隠しテキストボックスを探して更新
1695
  const jsUpdateTextbox = document.querySelector('#js_pose_update textarea');
@@ -1778,7 +1929,7 @@ function drawPose(poseData, enableHands = true, enableFace = true, highlightInde
1778
  ];
1779
 
1780
  // 🔧 Issue #043: 手データのデバッグ情報追加
1781
- console.log('🫳 Hand data debug:', {
1782
  enableHands: enableHands,
1783
  leftHandLength: handsDataForDrawing[0].length,
1784
  rightHandLength: handsDataForDrawing[1].length,
@@ -1788,14 +1939,14 @@ function drawPose(poseData, enableHands = true, enableFace = true, highlightInde
1788
  // 💖 people形式のみサポート、古いhands形式は削除
1789
  } else {
1790
  handsDataForDrawing = [[], []]; // 空の手データ
1791
- console.log('🚫 No people data available for hands');
1792
  }
1793
 
1794
  if (handsDataForDrawing && handsDataForDrawing.length >= 2) {
1795
- console.log('✅ Calling drawHands function');
1796
  drawHands(handsDataForDrawing, originalRes, scaleX, scaleY);
1797
  } else {
1798
- console.log('❌ Invalid hands data for drawing');
1799
  }
1800
  } catch (error) {
1801
  console.error("❌ Error drawing hands:", error);
@@ -2623,13 +2774,16 @@ function calculateHandRect(handData, originalRes, scaleX, scaleY) {
2623
  const confidence = handData[i + 2];
2624
 
2625
  if (confidence > 0.3) { // refs互換の閾値
2626
- const scaledX = x * scaleX;
2627
- const scaledY = y * scaleY;
2628
-
2629
- minX = Math.min(minX, scaledX);
2630
- minY = Math.min(minY, scaledY);
2631
- maxX = Math.max(maxX, scaledX);
2632
- maxY = Math.max(maxY, scaledY);
 
 
 
2633
  validPointCount++;
2634
  }
2635
  }
@@ -2638,12 +2792,61 @@ function calculateHandRect(handData, originalRes, scaleX, scaleY) {
2638
 
2639
  // 10px余白付きで矩形返却(refs互換)
2640
  const margin = 10;
2641
- return {
2642
  x: minX - margin,
2643
  y: minY - margin,
2644
  width: (maxX - minX) + (margin * 2),
2645
  height: (maxY - minY) + (margin * 2)
2646
  };
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2647
  }
2648
 
2649
  // 🔧 顔キーポイントから矩形を計算(refs互換)
@@ -2685,6 +2888,13 @@ function calculateFaceRect(faceData, originalRes, scaleX, scaleY) {
2685
  function drawEditableRect(ctx, rect, color, id) {
2686
  if (!rect) return;
2687
 
 
 
 
 
 
 
 
2688
  // 太め破線で矩形描画(refs互換)
2689
  ctx.strokeStyle = color;
2690
  ctx.lineWidth = 3;
@@ -2885,12 +3095,21 @@ function updateRectControlDrag(mouseX, mouseY) {
2885
  const controlPoint = window.poseEditorGlobals.draggedRectControl;
2886
  const rectType = window.poseEditorGlobals.rectEditMode;
2887
 
 
 
 
 
 
 
 
2888
 
2889
  if (!controlPoint) {
 
2890
  return;
2891
  }
2892
 
2893
  if (!rectType) {
 
2894
  return;
2895
  }
2896
 
@@ -2960,9 +3179,24 @@ function updateRectControlDrag(mouseX, mouseY) {
2960
  // 🔧 矩形を更新(累積変形防止のため直接設定)
2961
  window.poseEditorGlobals.currentRects[rectType] = newRect;
2962
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2963
 
2964
  // 🔧 手・顔キーポイントの座標も更新(直接矩形変換版)
2965
  if (controlPoint) {
 
2966
  transformKeypointsDirectly(rectType, originalRect, newRect);
2967
  }
2968
 
@@ -3004,10 +3238,10 @@ function initializeRectEditInfo() {
3004
  // 💥 ベースは初回のみ保存!編集済みデータで上書きしない
3005
  if (!window.poseEditorGlobals.baseOriginalKeypoints) {
3006
  window.poseEditorGlobals.baseOriginalKeypoints = JSON.parse(JSON.stringify(currentPoseData));
3007
- console.log('💖 [initializeRectEditInfo] Base original keypoints saved for FIRST editing session');
3008
 
3009
  // ベースデータの詳細確認
3010
- console.log('🔍 [initializeRectEditInfo] Base data details:', {
3011
  hasHandLeft: !!window.poseEditorGlobals.baseOriginalKeypoints.people?.[0]?.hand_left_keypoints_2d,
3012
  hasHandRight: !!window.poseEditorGlobals.baseOriginalKeypoints.people?.[0]?.hand_right_keypoints_2d,
3013
  hasFace: !!window.poseEditorGlobals.baseOriginalKeypoints.people?.[0]?.face_keypoints_2d,
@@ -3016,15 +3250,15 @@ function initializeRectEditInfo() {
3016
  faceLength: window.poseEditorGlobals.baseOriginalKeypoints.people?.[0]?.face_keypoints_2d?.length || 0
3017
  });
3018
  } else {
3019
- console.log('💖 [initializeRectEditInfo] Base original keypoints already exists - keeping original data');
3020
  }
3021
 
3022
  // 作業用は常にベースからコピー(編集済みデータではなく!)
3023
  window.poseEditorGlobals.originalKeypoints = JSON.parse(JSON.stringify(window.poseEditorGlobals.baseOriginalKeypoints));
3024
- console.log('💖 [initializeRectEditInfo] Working original keypoints restored from base for current editing session');
3025
 
3026
  // 作業用データの詳細確認
3027
- console.log('🔍 [initializeRectEditInfo] Working data details:', {
3028
  hasHandLeft: !!window.poseEditorGlobals.originalKeypoints.people?.[0]?.hand_left_keypoints_2d,
3029
  hasHandRight: !!window.poseEditorGlobals.originalKeypoints.people?.[0]?.hand_right_keypoints_2d,
3030
  hasFace: !!window.poseEditorGlobals.originalKeypoints.people?.[0]?.face_keypoints_2d,
@@ -3577,4 +3811,4 @@ function drawEstimatedConnections(candidates, originalRes, scaleX, scaleY) {
3577
  ctx.globalAlpha = 1.0; // 不透明に戻す
3578
  }
3579
 
3580
- // 🎨 pose_editor.js initialization complete
 
1
  // Canvas操作用JavaScript for dwpose-editor
2
 
3
+ // 🔧 最小限デバッグフラグ(必要時のみONにする)
4
+ window.poseEditorDebug = window.poseEditorDebug || {
5
+ rect: false,
6
+ hands: false,
7
+ send: false
8
+ };
9
+
10
  // グローバル変数
11
  window.poseEditorGlobals = {
12
  canvas: null,
 
600
  const currentRect = window.poseEditorGlobals.currentRects[rectType];
601
  if (currentRect) {
602
  window.poseEditorGlobals.originalRect = { ...currentRect };
603
+ // 🔧 このドラッグ操作の基準となる"元キーポイント"も現在の状態からスナップショット
604
+ // 以前はセッション開始時のベースを使っていたため、連続リサイズで倍率/方向が狂っていた
605
+ const currentPoseData = window.poseEditorGlobals.poseData || poseData;
606
+ if (currentPoseData) {
607
+ window.poseEditorGlobals.originalKeypoints = JSON.parse(JSON.stringify(currentPoseData));
608
+ }
609
  }
610
 
611
  // コントロールポイントドラッグ(リサイズ)
 
1069
  const canvasWidth = window.poseEditorGlobals.canvas ? window.poseEditorGlobals.canvas.width : 512;
1070
  const canvasHeight = window.poseEditorGlobals.canvas ? window.poseEditorGlobals.canvas.height : 512;
1071
 
1072
+ // 解像度情報の取得(metadata.resolution resolution → 512x512)
1073
+ let dataResolutionWidth = 512;
1074
+ let dataResolutionHeight = 512;
1075
+ if (currentPoseData.metadata && currentPoseData.metadata.resolution && Array.isArray(currentPoseData.metadata.resolution) && currentPoseData.metadata.resolution.length >= 2) {
1076
+ dataResolutionWidth = currentPoseData.metadata.resolution[0];
1077
+ dataResolutionHeight = currentPoseData.metadata.resolution[1];
1078
+ } else if (currentPoseData.resolution && Array.isArray(currentPoseData.resolution) && currentPoseData.resolution.length >= 2) {
1079
  dataResolutionWidth = currentPoseData.resolution[0];
1080
  dataResolutionHeight = currentPoseData.resolution[1];
1081
  }
 
1083
  const coordScaleX = canvasWidth / dataResolutionWidth;
1084
  const coordScaleY = canvasHeight / dataResolutionHeight;
1085
 
1086
+ // 正規化/ピクセル/Canvas座標を判定(元データ優先で判定)
1087
  let isNormalized = false;
1088
+ let isCanvasUnit = false;
1089
+ let sampleX = null, sampleY = null;
1090
+ if (originalTargetKeypoints.length > 0) {
1091
+ let overCanvasCount = 0;
1092
+ let validCount = 0;
1093
+ for (let i = 0; i < originalTargetKeypoints.length; i += 3) {
1094
+ if (i + 2 < originalTargetKeypoints.length && originalTargetKeypoints[i + 2] > 0) {
1095
+ const x = originalTargetKeypoints[i];
1096
+ const y = originalTargetKeypoints[i + 1];
1097
+ if (sampleX === null) { sampleX = x; sampleY = y; }
1098
+ validCount++;
1099
+ if (x > dataResolutionWidth * 1.01 || y > dataResolutionHeight * 1.01) {
1100
+ overCanvasCount++;
1101
+ }
1102
+ // 正規化判定
1103
+ if (x >= 0 && x <= 1 && y >= 0 && y <= 1) {
1104
+ isNormalized = true;
1105
+ break;
1106
+ }
1107
+ if (validCount >= 20) break; // サンプル十分
1108
  }
1109
  }
1110
+ if (!isNormalized && validCount > 0) {
1111
+ // 多数がデータ解像度を超える場合はCanvas座標とみなす
1112
+ isCanvasUnit = (overCanvasCount / validCount) > 0.5;
1113
+ }
1114
  }
1115
 
1116
+ // 🔍 デバッグログ: 手と顔の座標データ比較
1117
+ if (window.poseEditorDebug.rect) console.log(`🔍 [DEBUG ${rectType}] 座標データ分析:`, {
1118
+ rectType: rectType,
1119
+ isNormalized: isNormalized,
1120
+ isCanvasUnit: isCanvasUnit,
1121
+ sampleCoord: { x: sampleX, y: sampleY },
1122
+ originalRect: { x: originalRect.x, y: originalRect.y, width: originalRect.width, height: originalRect.height },
1123
+ newRect: { x: newRect.x, y: newRect.y, width: newRect.width, height: newRect.height },
1124
+ coordScale: { x: coordScaleX, y: coordScaleY },
1125
+ resolution: { data: dataResolutionWidth + 'x' + dataResolutionHeight, canvas: canvasWidth + 'x' + canvasHeight },
1126
+ keypointsLength: targetKeypoints.length
1127
+ });
1128
+
1129
 
1130
+ // 参照矩形は常にポイント群のBBox(Canvas座標)を使用して相対比を安定化
1131
+ let refRect = (function () {
1132
+ let minX = Infinity, minY = Infinity, maxX = -Infinity, maxY = -Infinity;
1133
+ for (let i = 0; i < originalTargetKeypoints.length; i += 3) {
1134
+ if (i + 2 >= originalTargetKeypoints.length) break;
1135
+ const conf = originalTargetKeypoints[i + 2];
1136
+ if (conf > 0.1) {
1137
+ let x = originalTargetKeypoints[i];
1138
+ let y = originalTargetKeypoints[i + 1];
1139
+ let cx, cy;
1140
+ if (isNormalized) {
1141
+ cx = (x * dataResolutionWidth) * coordScaleX;
1142
+ cy = (y * dataResolutionHeight) * coordScaleY;
1143
+ } else if (isCanvasUnit) {
1144
+ cx = x; cy = y;
1145
+ } else {
1146
+ cx = x * coordScaleX;
1147
+ cy = y * coordScaleY;
1148
+ }
1149
+ minX = Math.min(minX, cx);
1150
+ minY = Math.min(minY, cy);
1151
+ maxX = Math.max(maxX, cx);
1152
+ maxY = Math.max(maxY, cy);
1153
+ }
1154
+ }
1155
+ const margin = 8;
1156
+ const bx = isFinite(minX) ? minX - margin : originalRect.x;
1157
+ const by = isFinite(minY) ? minY - margin : originalRect.y;
1158
+ const bw = (isFinite(maxX) && isFinite(minX)) ? (maxX - minX) + margin * 2 : originalRect.width;
1159
+ const bh = (isFinite(maxY) && isFinite(minY)) ? (maxY - minY) + margin * 2 : originalRect.height;
1160
+ const rr = { x: bx, y: by, width: Math.max(1, bw), height: Math.max(1, bh) };
1161
+ if (window.poseEditorDebug.rect) console.log('🔧 [transformKeypointsDirectly] Using bbox as refRect', { refRect: rr, originalRect });
1162
+ return rr;
1163
+ })();
1164
+
1165
  // キーポイントを一括変換(元データから毎回変換)
1166
+ let debugSampleIndex = -1;
1167
  for (let i = 0; i < originalTargetKeypoints.length; i += 3) {
1168
  if (i + 2 < originalTargetKeypoints.length && i + 2 < targetKeypoints.length) {
1169
  const confidence = originalTargetKeypoints[i + 2];
 
1172
  let x = originalTargetKeypoints[i];
1173
  let y = originalTargetKeypoints[i + 1];
1174
 
1175
+ // データ座標→Canvas座標(入力の座標系に応じて)
1176
  let canvasX, canvasY;
1177
  if (isNormalized) {
1178
  canvasX = (x * dataResolutionWidth) * coordScaleX;
1179
  canvasY = (y * dataResolutionHeight) * coordScaleY;
1180
+ } else if (isCanvasUnit) {
1181
+ // 既にCanvas座標(過去のバグで混入している場合)
1182
+ canvasX = x;
1183
+ canvasY = y;
1184
  } else {
1185
  canvasX = x * coordScaleX;
1186
  canvasY = y * coordScaleY;
1187
  }
1188
 
1189
+ // 元矩形内での相対位置を計算(参照矩形に対して)
1190
+ let relativeX = (canvasX - refRect.x) / refRect.width;
1191
+ let relativeY = (canvasY - refRect.y) / refRect.height;
1192
+ // 安定化のために0-1へクランプ
1193
+ relativeX = Math.max(0, Math.min(1, relativeX));
1194
+ relativeY = Math.max(0, Math.min(1, relativeY));
1195
+
1196
  // 新矩形での新しい位置を計算
1197
  const newCanvasX = newRect.x + (relativeX * newRect.width);
1198
  const newCanvasY = newRect.y + (relativeY * newRect.height);
1199
 
1200
+ // Canvas座標→データ座標に戻す(常にデータ座標で保存)
1201
+ let finalX, finalY;
1202
  if (isNormalized) {
1203
  const dataX = newCanvasX / coordScaleX;
1204
  const dataY = newCanvasY / coordScaleY;
1205
+ finalX = dataX / dataResolutionWidth;
1206
+ finalY = dataY / dataResolutionHeight;
1207
+ targetKeypoints[i] = finalX;
1208
+ targetKeypoints[i + 1] = finalY;
1209
  } else {
1210
+ finalX = newCanvasX / coordScaleX;
1211
+ finalY = newCanvasY / coordScaleY;
1212
+ targetKeypoints[i] = finalX;
1213
+ targetKeypoints[i + 1] = finalY;
1214
+ }
1215
+
1216
+ // 🔍 最初のサンプルポイントのみ詳細ログ出力
1217
+ if (debugSampleIndex < 0) {
1218
+ debugSampleIndex = i / 3;
1219
+ if (window.poseEditorDebug.rect) console.log(`🔍 [DEBUG ${rectType}] 座標変換詳細 (Point ${debugSampleIndex}):`, {
1220
+ originalData: { x: x, y: y },
1221
+ canvasCoord: { x: canvasX, y: canvasY },
1222
+ relative: { x: relativeX, y: relativeY },
1223
+ newCanvas: { x: newCanvasX, y: newCanvasY },
1224
+ finalData: { x: finalX, y: finalY },
1225
+ deltaCanvas: { x: newCanvasX - canvasX, y: newCanvasY - canvasY },
1226
+ deltaData: { x: finalX - x, y: finalY - y }
1227
+ });
1228
  }
1229
  }
1230
  }
1231
  }
1232
 
1233
+ // 🔍 手のデータの場合:変換前後の全データ比較ログ
1234
+ if (rectType === 'leftHand' || rectType === 'rightHand') {
1235
+ if (window.poseEditorDebug.rect) console.log(`🔍 [${rectType}] 変換前後データ比較:`, {
1236
+ rectType: rectType,
1237
+ originalFirstPoint: originalTargetKeypoints.length >= 3 ?
1238
+ { x: originalTargetKeypoints[0], y: originalTargetKeypoints[1], conf: originalTargetKeypoints[2] } : null,
1239
+ transformedFirstPoint: targetKeypoints.length >= 3 ?
1240
+ { x: targetKeypoints[0], y: targetKeypoints[1], conf: targetKeypoints[2] } : null,
1241
+ originalSample3Points: [
1242
+ originalTargetKeypoints.slice(0, 9), // 最初の3点
1243
+ originalTargetKeypoints.slice(18, 27), // 中間3点
1244
+ originalTargetKeypoints.slice(54, 63) // 最後の3点
1245
+ ],
1246
+ transformedSample3Points: [
1247
+ targetKeypoints.slice(0, 9), // 最初の3点
1248
+ targetKeypoints.slice(18, 27), // 中間3点
1249
+ targetKeypoints.slice(54, 63) // 最後の3点
1250
+ ],
1251
+ coordinateShift: targetKeypoints.length >= 3 ? {
1252
+ deltaX: targetKeypoints[0] - originalTargetKeypoints[0],
1253
+ deltaY: targetKeypoints[1] - originalTargetKeypoints[1]
1254
+ } : null
1255
+ });
1256
+ }
1257
+
1258
  }
1259
 
1260
  // 🔧 元座標からキーポイントを移動(累積移動防止版)
 
1417
  return;
1418
  }
1419
 
1420
+ // Canvas→データ座標への変換係数
1421
+ let dataResolutionWidth = 512;
1422
+ let dataResolutionHeight = 512;
1423
+ if (currentPoseData.metadata && currentPoseData.metadata.resolution && Array.isArray(currentPoseData.metadata.resolution) && currentPoseData.metadata.resolution.length >= 2) {
1424
+ dataResolutionWidth = currentPoseData.metadata.resolution[0];
1425
+ dataResolutionHeight = currentPoseData.metadata.resolution[1];
1426
+ } else if (currentPoseData.resolution && Array.isArray(currentPoseData.resolution) && currentPoseData.resolution.length >= 2) {
1427
+ dataResolutionWidth = currentPoseData.resolution[0];
1428
+ dataResolutionHeight = currentPoseData.resolution[1];
1429
+ }
1430
+ const canvasWidth = window.poseEditorGlobals.canvas ? window.poseEditorGlobals.canvas.width : 512;
1431
+ const canvasHeight = window.poseEditorGlobals.canvas ? window.poseEditorGlobals.canvas.height : 512;
1432
+ const coordScaleX = canvasWidth / dataResolutionWidth;
1433
+ const coordScaleY = canvasHeight / dataResolutionHeight;
1434
+
1435
+ // Canvasの移動量→データ座標の移動量へ変換
1436
+ const dataDeltaX = deltaX / coordScaleX;
1437
+ const dataDeltaY = deltaY / coordScaleY;
1438
+
1439
+ // すべてのキーポイントを移動(データ座標系)
1440
  for (let i = 0; i < keypoints.length; i += 3) {
1441
  const confidence = keypoints[i + 2];
1442
 
1443
  if (confidence > 0.1) { // 有効なキーポイントのみ移動
1444
+ keypoints[i] += dataDeltaX; // X座標(データ座標)
1445
+ keypoints[i + 1] += dataDeltaY; // Y座標(データ座標)
1446
  }
1447
  }
1448
 
 
1705
 
1706
  // Gradioにデータ送信(refs互換・強制changeイベント版)
1707
  function sendPoseDataToGradio() {
1708
+ if (window.poseEditorDebug.send) console.log('🔍 [sendPoseDataToGradio] Called');
1709
  // グローバルposeDataを参照
1710
  const currentPoseData = window.poseEditorGlobals.poseData || poseData;
1711
+ if (window.poseEditorDebug.send) console.log('🔍 [sendPoseDataToGradio] currentPoseData state:', {
1712
  exists: !!currentPoseData,
1713
  hasPeople: !!currentPoseData?.people,
1714
  peopleCount: currentPoseData?.people?.length || 0,
 
1828
  }
1829
 
1830
  // 送信前の最終データ確認
1831
+ if (window.poseEditorDebug.send) console.log('🔍 [sendPoseDataToGradio] Final canvasData being sent:', {
1832
  hasPeople: !!canvasData.people,
1833
  peopleCount: canvasData.people?.length || 0,
1834
  hasHandLeft: !!canvasData.people?.[0]?.hand_left_keypoints_2d,
 
1840
  });
1841
 
1842
  const jsonString = JSON.stringify(canvasData);
1843
+ if (window.poseEditorDebug.send) console.log('🎯 [sendPoseDataToGradio] People形式でGradioに送信:', canvasData.people.length, 'people');
1844
 
1845
  // 専用の隠しテキストボックスを探して更新
1846
  const jsUpdateTextbox = document.querySelector('#js_pose_update textarea');
 
1929
  ];
1930
 
1931
  // 🔧 Issue #043: 手データのデバッグ情報追加
1932
+ if (window.poseEditorDebug.hands) console.log('🫳 Hand data debug:', {
1933
  enableHands: enableHands,
1934
  leftHandLength: handsDataForDrawing[0].length,
1935
  rightHandLength: handsDataForDrawing[1].length,
 
1939
  // 💖 people形式のみサポート、古いhands形式は削除
1940
  } else {
1941
  handsDataForDrawing = [[], []]; // 空の手データ
1942
+ if (window.poseEditorDebug.hands) console.log('🚫 No people data available for hands');
1943
  }
1944
 
1945
  if (handsDataForDrawing && handsDataForDrawing.length >= 2) {
1946
+ if (window.poseEditorDebug.hands) console.log('✅ Calling drawHands function');
1947
  drawHands(handsDataForDrawing, originalRes, scaleX, scaleY);
1948
  } else {
1949
+ if (window.poseEditorDebug.hands) console.log('❌ Invalid hands data for drawing');
1950
  }
1951
  } catch (error) {
1952
  console.error("❌ Error drawing hands:", error);
 
2774
  const confidence = handData[i + 2];
2775
 
2776
  if (confidence > 0.3) { // refs互換の閾値
2777
+ // 🔧 手のキーポイント描画と完全に統一した座標変換
2778
+ // drawHands関数では scaledX = x * scaleX しているため、
2779
+ // 矩形計算でも同じ変換を適用して座標系を統一
2780
+ const finalX = x * scaleX;
2781
+ const finalY = y * scaleY;
2782
+
2783
+ minX = Math.min(minX, finalX);
2784
+ minY = Math.min(minY, finalY);
2785
+ maxX = Math.max(maxX, finalX);
2786
+ maxY = Math.max(maxY, finalY);
2787
  validPointCount++;
2788
  }
2789
  }
 
2792
 
2793
  // 10px余白付きで矩形返却(refs互換)
2794
  const margin = 10;
2795
+ const rect = {
2796
  x: minX - margin,
2797
  y: minY - margin,
2798
  width: (maxX - minX) + (margin * 2),
2799
  height: (maxY - minY) + (margin * 2)
2800
  };
2801
+
2802
+ // 🔍 デバッグログ: 手の矩形計算結果
2803
+ const canvas = window.poseEditorGlobals.canvas;
2804
+
2805
+ // 🔍 全キーポイントの詳細ログを追加
2806
+ const allPoints = [];
2807
+ for (let i = 0; i < handData.length; i += 3) {
2808
+ if (i + 2 < handData.length && handData[i + 2] > 0.3) {
2809
+ allPoints.push({
2810
+ index: i / 3,
2811
+ x: handData[i],
2812
+ y: handData[i + 1],
2813
+ confidence: handData[i + 2]
2814
+ });
2815
+ }
2816
+ }
2817
+
2818
+ if (window.poseEditorDebug.rect) console.log(`🔍 [calculateHandRect] 矩形計算結果:`, {
2819
+ rawBounds: { minX, minY, maxX, maxY },
2820
+ finalRect: rect,
2821
+ validPointCount,
2822
+ firstPoint: handData.length >= 3 ? { x: handData[0], y: handData[1], conf: handData[2] } : null,
2823
+ scaleFactors: { scaleX, scaleY },
2824
+ coordinateDetection: handData[0] > 10 ? 'ピクセル座標' : '正規化座標',
2825
+ canvasInfo: canvas ? {
2826
+ width: canvas.width,
2827
+ height: canvas.height,
2828
+ clientWidth: canvas.clientWidth,
2829
+ clientHeight: canvas.clientHeight,
2830
+ scale: canvas.width / canvas.clientWidth
2831
+ } : 'Canvas未取得'
2832
+ });
2833
+
2834
+ // 🔍 キーポイント座標を詳細表示
2835
+ if (window.poseEditorDebug.rect) console.log(`🔍 [calculateHandRect] 全有効キーポイント座標:`, allPoints);
2836
+
2837
+ // 🔍 座標の範囲をさらに詳細分析
2838
+ const xCoords = allPoints.map(p => p.x);
2839
+ const yCoords = allPoints.map(p => p.y);
2840
+ if (window.poseEditorDebug.rect) console.log(`🔍 [calculateHandRect] 座標範囲詳細:`, {
2841
+ xCoords: xCoords,
2842
+ yCoords: yCoords,
2843
+ xRange: `${Math.min(...xCoords).toFixed(2)} 〜 ${Math.max(...xCoords).toFixed(2)}`,
2844
+ yRange: `${Math.min(...yCoords).toFixed(2)} 〜 ${Math.max(...yCoords).toFixed(2)}`,
2845
+ xSpread: (Math.max(...xCoords) - Math.min(...xCoords)).toFixed(2),
2846
+ ySpread: (Math.max(...yCoords) - Math.min(...yCoords)).toFixed(2)
2847
+ });
2848
+
2849
+ return rect;
2850
  }
2851
 
2852
  // 🔧 顔キーポイントから矩形を計算(refs互換)
 
2888
  function drawEditableRect(ctx, rect, color, id) {
2889
  if (!rect) return;
2890
 
2891
+ // 🔍 デバッグログ: 実際の描画座標
2892
+ if (window.poseEditorDebug.rect) console.log(`🔍 [drawEditableRect] ${id} 描画座標:`, {
2893
+ rect: { x: rect.x, y: rect.y, width: rect.width, height: rect.height },
2894
+ color,
2895
+ canvasTransform: ctx.getTransform()
2896
+ });
2897
+
2898
  // 太め破線で矩形描画(refs互換)
2899
  ctx.strokeStyle = color;
2900
  ctx.lineWidth = 3;
 
3095
  const controlPoint = window.poseEditorGlobals.draggedRectControl;
3096
  const rectType = window.poseEditorGlobals.rectEditMode;
3097
 
3098
+ // 🔍 関数呼び出しログ
3099
+ if (window.poseEditorDebug.rect) console.log(`🔍 [updateRectControlDrag] Called:`, {
3100
+ rectType: rectType,
3101
+ controlPoint: controlPoint,
3102
+ mousePos: { x: mouseX, y: mouseY },
3103
+ hasOriginalRect: !!window.poseEditorGlobals.originalRect
3104
+ });
3105
 
3106
  if (!controlPoint) {
3107
+ if (window.poseEditorDebug.rect) console.log(`⚠️ [updateRectControlDrag] No controlPoint, exiting`);
3108
  return;
3109
  }
3110
 
3111
  if (!rectType) {
3112
+ if (window.poseEditorDebug.rect) console.log(`⚠️ [updateRectControlDrag] No rectType, exiting`);
3113
  return;
3114
  }
3115
 
 
3179
  // 🔧 矩形を更新(累積変形防止のため直接設定)
3180
  window.poseEditorGlobals.currentRects[rectType] = newRect;
3181
 
3182
+ // 🔍 矩形変換ログ
3183
+ if (window.poseEditorDebug.rect) console.log(`🔍 [updateRectControlDrag] 矩形変換:`, {
3184
+ rectType: rectType,
3185
+ controlPosition: controlPoint.position,
3186
+ originalRect: originalRect,
3187
+ newRect: newRect,
3188
+ mousePos: { x: mouseX, y: mouseY },
3189
+ rectDelta: {
3190
+ x: newRect.x - originalRect.x,
3191
+ y: newRect.y - originalRect.y,
3192
+ width: newRect.width - originalRect.width,
3193
+ height: newRect.height - originalRect.height
3194
+ }
3195
+ });
3196
 
3197
  // 🔧 手・顔キーポイントの座標も更新(直接矩形変換版)
3198
  if (controlPoint) {
3199
+ if (window.poseEditorDebug.rect) console.log(`🔍 [updateRectControlDrag] Calling transformKeypointsDirectly...`);
3200
  transformKeypointsDirectly(rectType, originalRect, newRect);
3201
  }
3202
 
 
3238
  // 💥 ベースは初回のみ保存!編集済みデータで上書きしない
3239
  if (!window.poseEditorGlobals.baseOriginalKeypoints) {
3240
  window.poseEditorGlobals.baseOriginalKeypoints = JSON.parse(JSON.stringify(currentPoseData));
3241
+ if (window.poseEditorDebug.rect) console.log('💖 [initializeRectEditInfo] Base original keypoints saved for FIRST editing session');
3242
 
3243
  // ベースデータの詳細確認
3244
+ if (window.poseEditorDebug.rect) console.log('🔍 [initializeRectEditInfo] Base data details:', {
3245
  hasHandLeft: !!window.poseEditorGlobals.baseOriginalKeypoints.people?.[0]?.hand_left_keypoints_2d,
3246
  hasHandRight: !!window.poseEditorGlobals.baseOriginalKeypoints.people?.[0]?.hand_right_keypoints_2d,
3247
  hasFace: !!window.poseEditorGlobals.baseOriginalKeypoints.people?.[0]?.face_keypoints_2d,
 
3250
  faceLength: window.poseEditorGlobals.baseOriginalKeypoints.people?.[0]?.face_keypoints_2d?.length || 0
3251
  });
3252
  } else {
3253
+ if (window.poseEditorDebug.rect) console.log('💖 [initializeRectEditInfo] Base original keypoints already exists - keeping original data');
3254
  }
3255
 
3256
  // 作業用は常にベースからコピー(編集済みデータではなく!)
3257
  window.poseEditorGlobals.originalKeypoints = JSON.parse(JSON.stringify(window.poseEditorGlobals.baseOriginalKeypoints));
3258
+ if (window.poseEditorDebug.rect) console.log('💖 [initializeRectEditInfo] Working original keypoints restored from base for current editing session');
3259
 
3260
  // 作業用データの詳細確認
3261
+ if (window.poseEditorDebug.rect) console.log('🔍 [initializeRectEditInfo] Working data details:', {
3262
  hasHandLeft: !!window.poseEditorGlobals.originalKeypoints.people?.[0]?.hand_left_keypoints_2d,
3263
  hasHandRight: !!window.poseEditorGlobals.originalKeypoints.people?.[0]?.hand_right_keypoints_2d,
3264
  hasFace: !!window.poseEditorGlobals.originalKeypoints.people?.[0]?.face_keypoints_2d,
 
3811
  ctx.globalAlpha = 1.0; // 不透明に戻す
3812
  }
3813
 
3814
+ // 🎨 pose_editor.js initialization complete