gearmachine commited on
Commit
8c08729
·
1 Parent(s): 8de0c20

feat: テンプレート選択機能の改善と手動更新方式の実装

Browse files

- CLAUDE.md:
- テンプレート選択機能を改善し、ドロップダウンを「3頭身立ちポーズ」に集約
- 「🔄 ポーズ更新」ボタンを追加し、手動更新方式に変更
- 3heads.jsonファイルを直接活用するテンプレート読み込み機能を実装
- Python側でpeople形式とbodies形式のハイブリッドデータ構造を採用
- JavaScript側でテンプレート読み込み時の表示設定強制同期を実装
- 体・手・顔の全データが初期表示から正常に描画される仕組みを完成

- app.py:
- デバッグ用の手データ確認コードを削除し、コードをクリーンアップ

- pose_editor.js:
- 手データのデバッグログを削除し、コードをクリーンアップ

CLAUDE.md CHANGED
@@ -192,6 +192,14 @@ Key Features:
192
  * 詳細モードでは矩形を非表示にして、キーポイント直接操作を可能に
193
  * ラジオボタン監視機能の追加で編集モード切り替えをリアルタイム反映
194
 
 
 
 
 
 
 
 
 
195
  ### 🔧 **Current Implementation Status**
196
  **Phase 1 (Project Foundation)**: COMPLETED ✅
197
  - Basic Gradio application structure ✅
 
192
  * 詳細モードでは矩形を非表示にして、キーポイント直接操作を可能に
193
  * ラジオボタン監視機能の追加で編集モード切り替えをリアルタイム反映
194
 
195
+ - **Issue #039**: テンプレート選択機能改善 (2025-01-15) 🎯
196
+ * テンプレートドロップダウンを1つの選択肢(「3頭身立ちポーズ」)に集約
197
+ * 「🔄 ポーズ更新」ボタンを追加し、手動更新方式に変更
198
+ * 3heads.jsonファイルを直接活用するテンプレート読み込み機能を実装
199
+ * Python側でpeople形式とbodies形式のハイブリッドデータ構造を採用
200
+ * JavaScript側でテンプレート読み込み時の表示設定強制同期を実装
201
+ * 体・手・顔の全データが初期表示から正常に描画される仕組みを完成
202
+
203
  ### 🔧 **Current Implementation Status**
204
  **Phase 1 (Project Foundation)**: COMPLETED ✅
205
  - Basic Gradio application structure ✅
app.py CHANGED
@@ -325,40 +325,12 @@ def main():
325
  "face_keypoints_2d": actual_pose_data.get('faces', [[]])[0] if actual_pose_data.get('faces') else []
326
  }
327
 
328
- # デバッグ:手データの変換確認
329
- print(f"[DEBUG] 🫳 Hand data conversion:")
330
- print(f" - hands in data: {bool(actual_pose_data.get('hands'))}")
331
- print(f" - hands length: {len(actual_pose_data.get('hands', []))}")
332
- if actual_pose_data.get('hands'):
333
- print(f" - left hand length: {len(actual_pose_data['hands'][0]) if len(actual_pose_data['hands']) > 0 else 0}")
334
- print(f" - right hand length: {len(actual_pose_data['hands'][1]) if len(actual_pose_data['hands']) > 1 else 0}")
335
- print(f" - person_data left hand: {len(person_data['hand_left_keypoints_2d'])}")
336
- print(f" - person_data right hand: {len(person_data['hand_right_keypoints_2d'])}")
337
-
338
- # デバッグ:actual_pose_dataの構造確認
339
- print(f"[DEBUG] 🔍 actual_pose_data structure check:")
340
- print(f" - keys: {list(actual_pose_data.keys())}")
341
- print(f" - has bodies: {'bodies' in actual_pose_data}")
342
- if 'bodies' in actual_pose_data:
343
- print(f" - bodies keys: {list(actual_pose_data['bodies'].keys())}")
344
- print(f" - has candidate: {'candidate' in actual_pose_data['bodies']}")
345
-
346
  # bodies.candidateからpose_keypoints_2d変換
347
  if 'bodies' in actual_pose_data and 'candidate' in actual_pose_data['bodies']:
348
  candidates = actual_pose_data['bodies']['candidate']
349
- print(f"[DEBUG] 🤖 Body data conversion:")
350
- print(f" - bodies.candidate length: {len(candidates)}")
351
- for i, candidate in enumerate(candidates):
352
  if candidate and len(candidate) >= 2:
353
  person_data["pose_keypoints_2d"].extend([candidate[0], candidate[1], candidate[2] if len(candidate) > 2 else 1.0])
354
- if i < 3: # 最初の3つだけログ出力
355
- print(f" - candidate {i}: {candidate[:3]}")
356
- print(f" - final pose_keypoints_2d length: {len(person_data['pose_keypoints_2d'])}")
357
- else:
358
- print(f"[DEBUG] ❌ No bodies.candidate found in actual_pose_data!")
359
- print(f" - has bodies: {bool(actual_pose_data.get('bodies'))}")
360
- if actual_pose_data.get('bodies'):
361
- print(f" - bodies keys: {list(actual_pose_data['bodies'].keys())}")
362
 
363
  _current_poses = [{
364
  'people': [person_data],
@@ -367,8 +339,6 @@ def main():
367
  }
368
  }]
369
  _current_frame_index = 0
370
- print(f"[DEBUG] ✅ グローバル変数更新完了(テンプレート読み込み・refs互換): {template_name}")
371
-
372
  # people形式でデータを構築(元のbodies/hands/facesも保持)
373
  people_format_data = {
374
  'people': [person_data],
@@ -381,8 +351,6 @@ def main():
381
  'is_template_load': True # テンプレート読み込みフラグ
382
  }
383
 
384
- print(f"[DEBUG] 🔄 Returning people format data with {len(person_data['hand_left_keypoints_2d'])} left hand points")
385
-
386
  notify_success(f"{template_name}を読み込みました")
387
  return people_format_data, people_format_data
388
  else:
 
325
  "face_keypoints_2d": actual_pose_data.get('faces', [[]])[0] if actual_pose_data.get('faces') else []
326
  }
327
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
328
  # bodies.candidateからpose_keypoints_2d変換
329
  if 'bodies' in actual_pose_data and 'candidate' in actual_pose_data['bodies']:
330
  candidates = actual_pose_data['bodies']['candidate']
331
+ for candidate in candidates:
 
 
332
  if candidate and len(candidate) >= 2:
333
  person_data["pose_keypoints_2d"].extend([candidate[0], candidate[1], candidate[2] if len(candidate) > 2 else 1.0])
 
 
 
 
 
 
 
 
334
 
335
  _current_poses = [{
336
  'people': [person_data],
 
339
  }
340
  }]
341
  _current_frame_index = 0
 
 
342
  # people形式でデータを構築(元のbodies/hands/facesも保持)
343
  people_format_data = {
344
  'people': [person_data],
 
351
  'is_template_load': True # テンプレート読み込みフラグ
352
  }
353
 
 
 
354
  notify_success(f"{template_name}を読み込みました")
355
  return people_format_data, people_format_data
356
  else:
issues/039_テンプレート選択機能改善.md CHANGED
@@ -131,6 +131,27 @@ template_update_btn = gr.Button(
131
 
132
  ---
133
  *Created: 2025-01-15*
134
- *Status: 新規作成*
 
135
  *Assignee: Claude Code Agent*
136
- *Tags: UI改善, テンプレート機能, ユーザビリティ*
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
131
 
132
  ---
133
  *Created: 2025-01-15*
134
+ *Completed: 2025-01-15*
135
+ *Status: ✅ 完了*
136
  *Assignee: Claude Code Agent*
137
+ *Tags: UI改善, テンプレート機能, ユーザビリティ*
138
+
139
+ ## 実装結果
140
+
141
+ ### ✅ 完了した機能
142
+ 1. **テンプレートUI改善**: ドロップダウンを「3頭身立ちポーズ」1つに集約
143
+ 2. **手動更新ボタン**: 「🔄 ポーズ更新」ボタンの追加と手動更新方式への変更
144
+ 3. **3heads.json活用**: 実際のテンプレートファイルを直接読み込む機能の実装
145
+ 4. **ハイブリッドデータ構造**: people形式とbodies形式を両方含むデータ送信
146
+ 5. **表示設定同期**: テンプレート読み込み時の表示設定強制同期機能
147
+
148
+ ### 🛠️ 技術的解決策
149
+ - **Python側**: people形式とbodies/hands/faces形式のハイブリッド送信
150
+ - **JavaScript側**: テンプレート読み込み時の表示設定強制取得
151
+ - **初期表示問題解決**: 体・手・顔の全データが最初から正常描画される
152
+
153
+ ### 📊 成果
154
+ - シンプルで分かりやすいUI(選択肢1つ)
155
+ - ユーザー制御可能な手動更新方式
156
+ - 3heads.jsonテンプレートファイルの有効活用
157
+ - 全ポーズデータの完全な初期表示
static/pose_editor.js CHANGED
@@ -1876,23 +1876,14 @@ function drawPose(poseData, enableHands = true, enableFace = true, highlightInde
1876
  person.hand_left_keypoints_2d || [],
1877
  person.hand_right_keypoints_2d || []
1878
  ];
1879
- console.log('🫳 [HANDS DEBUG] Hand data prepared:', {
1880
- leftLength: handsDataForDrawing[0].length,
1881
- rightLength: handsDataForDrawing[1].length,
1882
- leftSample: handsDataForDrawing[0].slice(0, 9),
1883
- rightSample: handsDataForDrawing[1].slice(0, 9)
1884
- });
1885
  // 💖 people形式のみサポート、古いhands形式は削除
1886
  } else {
1887
  handsDataForDrawing = [[], []]; // 空の手データ
1888
- console.log('🫳 [HANDS DEBUG] No people data, using empty hands');
1889
  }
1890
 
1891
  if (handsDataForDrawing && handsDataForDrawing.length >= 2) {
1892
  drawHands(handsDataForDrawing, originalRes, scaleX, scaleY);
1893
  debugLog(`🫳 Hands drawn: left=${handsDataForDrawing[0].length}, right=${handsDataForDrawing[1].length}`);
1894
- } else {
1895
- console.log('🫳 [HANDS DEBUG] Invalid handsDataForDrawing structure');
1896
  }
1897
  } catch (error) {
1898
  console.error("❌ Error drawing hands:", error);
@@ -2363,28 +2354,6 @@ window.gradioCanvasUpdate = function(pose_json_str) {
2363
  }
2364
 
2365
  // 💖 グローバルposeDataを更新(但し、people形式チェック付き)
2366
- console.log('🔍 [gradioCanvasUpdate] Received poseData structure:', {
2367
- hasPeople: !!poseData?.people,
2368
- peopleCount: poseData?.people?.length || 0,
2369
- hasHandLeft: !!poseData?.people?.[0]?.hand_left_keypoints_2d,
2370
- hasHandRight: !!poseData?.people?.[0]?.hand_right_keypoints_2d,
2371
- hasFace: !!poseData?.people?.[0]?.face_keypoints_2d,
2372
- hasBodies: !!poseData?.bodies,
2373
- hasHands: !!poseData?.hands,
2374
- hasFaces: !!poseData?.faces,
2375
- hasBackgroundImage: !!poseData?.background_image,
2376
- handLeftLength: poseData?.people?.[0]?.hand_left_keypoints_2d?.length || 0,
2377
- handRightLength: poseData?.people?.[0]?.hand_right_keypoints_2d?.length || 0,
2378
- isTemplateLoad: !!poseData?.is_template_load
2379
- });
2380
-
2381
- // 🔍 テンプレート読み込み時の詳細ログ
2382
- if (poseData?.is_template_load) {
2383
- console.log('🔄 [TEMPLATE LOAD] Detailed hand data check:', {
2384
- leftHandData: poseData?.people?.[0]?.hand_left_keypoints_2d?.slice(0, 9) || 'NO DATA',
2385
- rightHandData: poseData?.people?.[0]?.hand_right_keypoints_2d?.slice(0, 9) || 'NO DATA'
2386
- });
2387
- }
2388
 
2389
  // 🎨 背景画像が含まれている場合は設定
2390
  if (poseData && poseData.background_image) {
@@ -2398,31 +2367,19 @@ window.gradioCanvasUpdate = function(pose_json_str) {
2398
  const existingPoseData = window.poseEditorGlobals.poseData;
2399
 
2400
  if (isTemplateLoad) {
2401
- console.log('🔄 [gradioCanvasUpdate] Template load detected - complete replacement');
2402
  // テンプレート読み込み時は完全置換
2403
  window.poseEditorGlobals.poseData = poseData;
2404
  // baseOriginalKeypointsもリセット(新しいテンプレート用)
2405
  window.poseEditorGlobals.baseOriginalKeypoints = null;
2406
 
2407
- // 🔧 テンプレート読み込み時は現在のチェックボックス状態を強制取得
2408
  updateDisplaySettingsFromCheckbox();
2409
- console.log('🔧 [TEMPLATE LOAD] Forced display settings update:', {
2410
- enableHands: window.poseEditorGlobals.enableHands,
2411
- enableFace: window.poseEditorGlobals.enableFace,
2412
- editMode: window.poseEditorGlobals.editMode
2413
- });
2414
 
2415
- // 念のため、チェックボックス監視も再設定
2416
  setTimeout(() => setupGradioCheckboxListeners(), 100);
2417
  } else if (existingPoseData && existingPoseData.people && existingPoseData.people[0] &&
2418
  poseData && (!poseData.people || !poseData.people[0])) {
2419
 
2420
- console.log('🛡️ [gradioCanvasUpdate] Protecting existing people data from overwrite');
2421
- console.log('🔍 [gradioCanvasUpdate] Existing data:', {
2422
- hasHandLeft: !!existingPoseData.people[0].hand_left_keypoints_2d,
2423
- hasHandRight: !!existingPoseData.people[0].hand_right_keypoints_2d,
2424
- hasFace: !!existingPoseData.people[0].face_keypoints_2d
2425
- });
2426
 
2427
  // Python側データを古い形式に変換してからマージ
2428
  if (poseData.bodies || poseData.hands || poseData.faces) {
@@ -2441,7 +2398,6 @@ window.gradioCanvasUpdate = function(pose_json_str) {
2441
  }
2442
 
2443
  // 手と顔データは既存を保持(Python側で消失している可能性があるため)
2444
- console.log('🔍 [gradioCanvasUpdate] Preserving hands/face data from existing poseData');
2445
 
2446
  window.poseEditorGlobals.poseData = preservedPoseData;
2447
  poseData = preservedPoseData; // 描画用も更新
@@ -2456,30 +2412,14 @@ window.gradioCanvasUpdate = function(pose_json_str) {
2456
 
2457
  // 💖 originalKeypointsも設定(但し、baseOriginalKeypointsは保護)
2458
  if (poseData && poseData.people && poseData.people[0]) {
2459
- // 💥 baseOriginalKeypointsは上書きしない(編集セッション保持のため)
2460
  if (!window.poseEditorGlobals.baseOriginalKeypoints) {
2461
  window.poseEditorGlobals.baseOriginalKeypoints = JSON.parse(JSON.stringify(poseData));
2462
- console.log("💖 [gradioCanvasUpdate] baseOriginalKeypoints set for FIRST time");
2463
- } else {
2464
- console.log("💖 [gradioCanvasUpdate] baseOriginalKeypoints PROTECTED from overwrite");
2465
  }
2466
 
2467
  // originalKeypointsは更新してOK(作業用)
2468
  window.poseEditorGlobals.originalKeypoints = JSON.parse(JSON.stringify(poseData));
2469
- console.log("🔧 [gradioCanvasUpdate] originalKeypoints set from gradioCanvasUpdate for template/upload");
2470
 
2471
- // デバッグ:手のデータ確認
2472
- const person = poseData.people[0];
2473
- if (person.hand_left_keypoints_2d) {
2474
- debugLog(`🫳 Left hand keypoints: ${person.hand_left_keypoints_2d.length} elements`);
2475
- } else {
2476
- debugLog("🫳 No left hand keypoints found");
2477
- }
2478
- if (person.hand_right_keypoints_2d) {
2479
- debugLog(`🫳 Right hand keypoints: ${person.hand_right_keypoints_2d.length} elements`);
2480
- } else {
2481
- debugLog("🫳 No right hand keypoints found");
2482
- }
2483
  }
2484
 
2485
  if (!isCanvasReady()) {
@@ -2498,16 +2438,8 @@ window.gradioCanvasUpdate = function(pose_json_str) {
2498
  const ctx = window.poseEditorGlobals.ctx;
2499
  ctx.clearRect(0, 0, canvas.width, canvas.height);
2500
 
2501
- // 🔧 グローバル設定で描画(手・顔表示設定を反映)
2502
  if (poseData && Object.keys(poseData).length > 0) {
2503
- console.log('🎨 [DRAW CALL] About to call drawPose with settings:', {
2504
- enableHands: window.poseEditorGlobals.enableHands,
2505
- enableFace: window.poseEditorGlobals.enableFace,
2506
- editMode: window.poseEditorGlobals.editMode,
2507
- hasPeopleData: !!poseData.people,
2508
- handDataAvailable: !!(poseData.people?.[0]?.hand_left_keypoints_2d?.length > 0)
2509
- });
2510
-
2511
  drawPose(
2512
  poseData,
2513
  window.poseEditorGlobals.enableHands,
 
1876
  person.hand_left_keypoints_2d || [],
1877
  person.hand_right_keypoints_2d || []
1878
  ];
 
 
 
 
 
 
1879
  // 💖 people形式のみサポート、古いhands形式は削除
1880
  } else {
1881
  handsDataForDrawing = [[], []]; // 空の手データ
 
1882
  }
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);
 
2354
  }
2355
 
2356
  // 💖 グローバルposeDataを更新(但し、people形式チェック付き)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2357
 
2358
  // 🎨 背景画像が含まれている場合は設定
2359
  if (poseData && poseData.background_image) {
 
2367
  const existingPoseData = window.poseEditorGlobals.poseData;
2368
 
2369
  if (isTemplateLoad) {
 
2370
  // テンプレート読み込み時は完全置換
2371
  window.poseEditorGlobals.poseData = poseData;
2372
  // baseOriginalKeypointsもリセット(新しいテンプレート用)
2373
  window.poseEditorGlobals.baseOriginalKeypoints = null;
2374
 
2375
+ // テンプレート読み込み時は現在のチェックボックス状態を強制取得
2376
  updateDisplaySettingsFromCheckbox();
 
 
 
 
 
2377
 
2378
+ // チェックボックス監視も再設定
2379
  setTimeout(() => setupGradioCheckboxListeners(), 100);
2380
  } else if (existingPoseData && existingPoseData.people && existingPoseData.people[0] &&
2381
  poseData && (!poseData.people || !poseData.people[0])) {
2382
 
 
 
 
 
 
 
2383
 
2384
  // Python側データを古い形式に変換してからマージ
2385
  if (poseData.bodies || poseData.hands || poseData.faces) {
 
2398
  }
2399
 
2400
  // 手と顔データは既存を保持(Python側で消失している可能性があるため)
 
2401
 
2402
  window.poseEditorGlobals.poseData = preservedPoseData;
2403
  poseData = preservedPoseData; // 描画用も更新
 
2412
 
2413
  // 💖 originalKeypointsも設定(但し、baseOriginalKeypointsは保護)
2414
  if (poseData && poseData.people && poseData.people[0]) {
2415
+ // baseOriginalKeypointsは上書きしない(編集セッション保持のため)
2416
  if (!window.poseEditorGlobals.baseOriginalKeypoints) {
2417
  window.poseEditorGlobals.baseOriginalKeypoints = JSON.parse(JSON.stringify(poseData));
 
 
 
2418
  }
2419
 
2420
  // originalKeypointsは更新してOK(作業用)
2421
  window.poseEditorGlobals.originalKeypoints = JSON.parse(JSON.stringify(poseData));
 
2422
 
 
 
 
 
 
 
 
 
 
 
 
 
2423
  }
2424
 
2425
  if (!isCanvasReady()) {
 
2438
  const ctx = window.poseEditorGlobals.ctx;
2439
  ctx.clearRect(0, 0, canvas.width, canvas.height);
2440
 
2441
+ // グローバル設定で描画(手・顔表示設定を反映)
2442
  if (poseData && Object.keys(poseData).length > 0) {
 
 
 
 
 
 
 
 
2443
  drawPose(
2444
  poseData,
2445
  window.poseEditorGlobals.enableHands,