grmchn Claude commited on
Commit
769b254
·
1 Parent(s): c63ce19

feat: JSONフォーマットをpeople形式に統一

Browse files

- export_utils.py: export_pose_as_json()をpeople形式出力に変更
- app.py: JSON読み込み処理でpeople形式に対応
- 既存のbodies形式との互換性を維持
- canvas_width/canvas_heightをサポート
- データ変換処理を簡素化してパフォーマンス向上

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>

Files changed (2) hide show
  1. app.py +80 -45
  2. utils/export_utils.py +68 -13
app.py CHANGED
@@ -462,47 +462,25 @@ def main():
462
  return gr.update(visible=False)
463
 
464
  def export_json(pose_data):
465
- """ポーズJSONをエクスポート(Button + File方式)(refs互換・グローバル管理)"""
466
  global _current_poses, _current_frame_index
467
 
468
- # refsと同じ_current_posesから最新データを取得
469
  export_data = None
470
  if _current_poses and 0 <= _current_frame_index < len(_current_poses):
471
  current_frame = _current_poses[_current_frame_index]
472
  if current_frame['people'] and current_frame['people'][0]:
473
- person = current_frame['people'][0]
474
-
475
- # people形式からbodies.candidate形式に変換(エクスポート用)
476
  export_data = {
477
- 'bodies': {'candidate': [], 'subset': []},
478
- 'hands': [],
479
- 'faces': [],
480
  'resolution': current_frame['metadata'].get('resolution', [512, 512])
481
  }
482
-
483
- # pose_keypoints_2d をcandidate形式に変換
484
- if 'pose_keypoints_2d' in person:
485
- pose_keypoints = person['pose_keypoints_2d']
486
- for i in range(0, len(pose_keypoints), 3):
487
- if i + 2 < len(pose_keypoints):
488
- x = pose_keypoints[i]
489
- y = pose_keypoints[i + 1]
490
- conf = pose_keypoints[i + 2]
491
- export_data['bodies']['candidate'].append([x, y, conf, 0])
492
-
493
- # 手と顔データ
494
- left_hand = person.get('hand_left_keypoints_2d', [])
495
- right_hand = person.get('hand_right_keypoints_2d', [])
496
- face_data = person.get('face_keypoints_2d', [])
497
-
498
- export_data['hands'] = [left_hand, right_hand]
499
- export_data['faces'] = [face_data] if face_data else []
500
 
501
  # フォールバック:引数のpose_dataを使用
502
  if export_data is None:
503
  export_data = pose_data
504
 
505
- print(f"[DEBUG] 📥 JSONダウンロードボタンクリック")
506
  print(f"[DEBUG] 📊 グローバルデータ: {bool(_current_poses)}, 引数データ: {bool(pose_data)}")
507
  print(f"[DEBUG] 📊 使用データ: {bool(export_data)}")
508
 
@@ -512,6 +490,7 @@ def main():
512
  return gr.update(visible=False)
513
 
514
  try:
 
515
  json_str = export_pose_as_json(export_data)
516
  if not json_str:
517
  print(f"[DEBUG] ❌ JSON生成失敗")
@@ -525,7 +504,7 @@ def main():
525
  f.write(json_str)
526
 
527
  print(f"[DEBUG] ✅ ファイル準備完了: {temp_path}")
528
- notify_success(f"JSONをエクスポートしました: {filename}")
529
 
530
  # 🎯 Button + File方式でファイルを表示
531
  return gr.update(value=temp_path, visible=True)
@@ -665,7 +644,7 @@ def main():
665
  print(f"[DEBUG] 🔓 データ更新処理完了 - フラグ解除: {old_flag} → {_is_updating} (timestamp={update_timestamp})")
666
 
667
  def on_json_upload(file):
668
- """JSONファイルアップロード時の処理(refs互換・グローバル管理)"""
669
  global _current_poses, _current_frame_index
670
 
671
  if file is None:
@@ -679,18 +658,46 @@ def main():
679
  # JSONをパース
680
  loaded_data = json.loads(json_content)
681
 
682
- # データ検証
683
- if not validate_pose_json(loaded_data):
684
- notify_error("無効なポーズデータフォーマットです")
685
- return None, {}
686
-
687
- # メタデータを除去(存在する場合)
688
- if 'metadata' in loaded_data:
689
- del loaded_data['metadata']
690
-
691
- # グローバル変数に保存(refs互換)
692
- if loaded_data:
693
- # bodies.candidate形式からpeople形式に変換
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
694
  person_data = {
695
  "pose_keypoints_2d": [],
696
  "hand_left_keypoints_2d": loaded_data.get('hands', [[], []])[0] if loaded_data.get('hands') else [],
@@ -712,10 +719,9 @@ def main():
712
  }
713
  }]
714
  _current_frame_index = 0
715
- print(f"[DEBUG] ✅ グローバル変数更新完了(JSON読み込み・refs互換)")
716
-
717
- notify_success("JSONファイルを読み込みました")
718
- return loaded_data, loaded_data
719
 
720
  except json.JSONDecodeError as e:
721
  notify_error(f"JSONパースエラー: {str(e)}")
@@ -723,6 +729,35 @@ def main():
723
  except Exception as e:
724
  notify_error(f"ファイル読み込みエラー: {str(e)}")
725
  return None, {}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
726
 
727
  def validate_pose_json(data):
728
  """ポーズJSONデータの検証"""
 
462
  return gr.update(visible=False)
463
 
464
  def export_json(pose_data):
465
+ """ポーズJSONをエクスポート(people形式・refs互換・グローバル管理)"""
466
  global _current_poses, _current_frame_index
467
 
468
+ # refsと同じ_current_posesから最新データを取得(簡素化)
469
  export_data = None
470
  if _current_poses and 0 <= _current_frame_index < len(_current_poses):
471
  current_frame = _current_poses[_current_frame_index]
472
  if current_frame['people'] and current_frame['people'][0]:
473
+ # 💖 people形式で直接エクスポート(変換不要)
 
 
474
  export_data = {
475
+ 'people': current_frame['people'],
 
 
476
  'resolution': current_frame['metadata'].get('resolution', [512, 512])
477
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
478
 
479
  # フォールバック:引数のpose_dataを使用
480
  if export_data is None:
481
  export_data = pose_data
482
 
483
+ print(f"[DEBUG] 📥 JSONダウンロードボタンクリック(people形式)")
484
  print(f"[DEBUG] 📊 グローバルデータ: {bool(_current_poses)}, 引数データ: {bool(pose_data)}")
485
  print(f"[DEBUG] 📊 使用データ: {bool(export_data)}")
486
 
 
490
  return gr.update(visible=False)
491
 
492
  try:
493
+ # export_pose_as_json()が新しいpeople形式で出力
494
  json_str = export_pose_as_json(export_data)
495
  if not json_str:
496
  print(f"[DEBUG] ❌ JSON生成失敗")
 
504
  f.write(json_str)
505
 
506
  print(f"[DEBUG] ✅ ファイル準備完了: {temp_path}")
507
+ notify_success(f"people形式JSONをエクスポートしました: {filename}")
508
 
509
  # 🎯 Button + File方式でファイルを表示
510
  return gr.update(value=temp_path, visible=True)
 
644
  print(f"[DEBUG] 🔓 データ更新処理完了 - フラグ解除: {old_flag} → {_is_updating} (timestamp={update_timestamp})")
645
 
646
  def on_json_upload(file):
647
+ """JSONファイルアップロード時の処理(people形式対応・refs互換)"""
648
  global _current_poses, _current_frame_index
649
 
650
  if file is None:
 
658
  # JSONをパース
659
  loaded_data = json.loads(json_content)
660
 
661
+ # people形式(配列)かどうかチェック
662
+ if isinstance(loaded_data, list) and len(loaded_data) > 0:
663
+ # 新しいpeople形式
664
+ frame_data = loaded_data[0] # 最初のフレームを使用
665
+
666
+ if 'people' in frame_data and frame_data['people']:
667
+ person_data = frame_data['people'][0]
668
+ canvas_width = frame_data.get('canvas_width', 512)
669
+ canvas_height = frame_data.get('canvas_height', 512)
670
+
671
+ # グローバル変数に保存(refs互換)
672
+ _current_poses = [{
673
+ 'people': [person_data],
674
+ 'metadata': {
675
+ 'resolution': [canvas_width, canvas_height]
676
+ }
677
+ }]
678
+ _current_frame_index = 0
679
+
680
+ # 表示用データ構築(互換性維持)
681
+ display_data = convert_people_to_bodies_format(person_data, [canvas_width, canvas_height])
682
+
683
+ print(f"[DEBUG] ✅ people形式JSON読み込み完了")
684
+ notify_success("people形式JSONファイルを読み込みました")
685
+ return display_data, display_data
686
+ else:
687
+ notify_error("無効なpeople形式データです")
688
+ return None, {}
689
+
690
+ else:
691
+ # 従来のbodies形式(互換性維持)
692
+ if not validate_pose_json(loaded_data):
693
+ notify_error("無効なポーズデータフォーマットです")
694
+ return None, {}
695
+
696
+ # メタデータを除去(存在する場合)
697
+ if 'metadata' in loaded_data:
698
+ del loaded_data['metadata']
699
+
700
+ # bodies形式からpeople形式に変換してグローバル保存
701
  person_data = {
702
  "pose_keypoints_2d": [],
703
  "hand_left_keypoints_2d": loaded_data.get('hands', [[], []])[0] if loaded_data.get('hands') else [],
 
719
  }
720
  }]
721
  _current_frame_index = 0
722
+ print(f"[DEBUG] ✅ bodies形式JSON読み込み・変換完了(互換性維持)")
723
+ notify_success("bodies形式JSONファイルを読み込みました(people形式に変換)")
724
+ return loaded_data, loaded_data
 
725
 
726
  except json.JSONDecodeError as e:
727
  notify_error(f"JSONパースエラー: {str(e)}")
 
729
  except Exception as e:
730
  notify_error(f"ファイル読み込みエラー: {str(e)}")
731
  return None, {}
732
+
733
+ def convert_people_to_bodies_format(person_data, resolution):
734
+ """people形式からbodies形式に変換(表示互換性用)"""
735
+ bodies_data = {
736
+ 'bodies': {'candidate': [], 'subset': []},
737
+ 'hands': [],
738
+ 'faces': [],
739
+ 'resolution': resolution
740
+ }
741
+
742
+ # pose_keypoints_2d をcandidate形式に変換
743
+ if 'pose_keypoints_2d' in person_data:
744
+ pose_keypoints = person_data['pose_keypoints_2d']
745
+ for i in range(0, len(pose_keypoints), 3):
746
+ if i + 2 < len(pose_keypoints):
747
+ x = pose_keypoints[i]
748
+ y = pose_keypoints[i + 1]
749
+ conf = pose_keypoints[i + 2]
750
+ bodies_data['bodies']['candidate'].append([x, y, conf, 0])
751
+
752
+ # 手と顔データ
753
+ left_hand = person_data.get('hand_left_keypoints_2d', [])
754
+ right_hand = person_data.get('hand_right_keypoints_2d', [])
755
+ face_data = person_data.get('face_keypoints_2d', [])
756
+
757
+ bodies_data['hands'] = [left_hand, right_hand]
758
+ bodies_data['faces'] = [face_data] if face_data else []
759
+
760
+ return bodies_data
761
 
762
  def validate_pose_json(data):
763
  """ポーズJSONデータの検証"""
utils/export_utils.py CHANGED
@@ -224,31 +224,86 @@ def draw_faces_on_image(draw, faces_data, canvas_size):
224
  # refs準拠: OpenCV(255,255,255)BGR → PIL(255,255,255)RGB = 白
225
  draw.ellipse([int(x)-2, int(y)-2, int(x)+2, int(y)+2], fill=(255, 255, 255))
226
 
227
- def export_pose_as_json(pose_data, include_metadata=True):
228
  """
229
- ポーズデータをJSONとして出力
230
 
231
  Args:
232
- pose_data: DWPoseデータ
233
- include_metadata: メタデータを含めるかどうか
234
 
235
  Returns:
236
- str: JSON文字列
237
  """
238
  try:
239
  if not pose_data:
240
  notify_error("ポーズデータがありません")
241
  return None
242
 
243
- export_data = pose_data.copy()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
244
 
245
- if include_metadata:
246
- export_data['metadata'] = {
247
- 'format': 'dwpose-editor',
248
- 'version': '1.0',
249
- 'exported_at': str(np.datetime64('now')),
250
- 'description': '2頭身・3頭身キャラクター用ポーズデータ'
251
- }
252
 
253
  json_str = json.dumps(export_data, indent=2, ensure_ascii=False)
254
  # 通知はapp.py側で行う(重複回避)
 
224
  # refs準拠: OpenCV(255,255,255)BGR → PIL(255,255,255)RGB = 白
225
  draw.ellipse([int(x)-2, int(y)-2, int(x)+2, int(y)+2], fill=(255, 255, 255))
226
 
227
+ def export_pose_as_json(pose_data, include_metadata=False):
228
  """
229
+ ポーズデータをpeople形式のJSONとして出力
230
 
231
  Args:
232
+ pose_data: DWPoseデータ(people形式またはbodies形式)
233
+ include_metadata: メタデータを含めるかどうか(デフォルト: False)
234
 
235
  Returns:
236
+ str: people形式のJSON文字列
237
  """
238
  try:
239
  if not pose_data:
240
  notify_error("ポーズデータがありません")
241
  return None
242
 
243
+ # people形式の出力データ構造を作成
244
+ export_data = []
245
+
246
+ # デフォルトの解像度
247
+ canvas_width = 512
248
+ canvas_height = 512
249
+
250
+ # pose_dataから解像度情報を取得
251
+ if 'resolution' in pose_data and pose_data['resolution']:
252
+ resolution = pose_data['resolution']
253
+ if isinstance(resolution, list) and len(resolution) >= 2:
254
+ canvas_width = int(resolution[0])
255
+ canvas_height = int(resolution[1])
256
+ elif 'metadata' in pose_data and 'resolution' in pose_data['metadata']:
257
+ resolution = pose_data['metadata']['resolution']
258
+ if isinstance(resolution, list) and len(resolution) >= 2:
259
+ canvas_width = int(resolution[0])
260
+ canvas_height = int(resolution[1])
261
+
262
+ # people形式データの構築
263
+ person_data = {
264
+ "pose_keypoints_2d": [],
265
+ "face_keypoints_2d": [],
266
+ "hand_left_keypoints_2d": [],
267
+ "hand_right_keypoints_2d": []
268
+ }
269
+
270
+ # people形式が既に存在する場合はそのまま使用
271
+ if 'people' in pose_data and pose_data['people']:
272
+ person_data = pose_data['people'][0].copy()
273
+ else:
274
+ # bodies形式からpeople形式に変換
275
+ if 'bodies' in pose_data and 'candidate' in pose_data['bodies']:
276
+ candidates = pose_data['bodies']['candidate']
277
+ for candidate in candidates:
278
+ if candidate and len(candidate) >= 2:
279
+ person_data["pose_keypoints_2d"].extend([
280
+ candidate[0],
281
+ candidate[1],
282
+ candidate[2] if len(candidate) > 2 else 1.0
283
+ ])
284
+
285
+ # 手データ
286
+ if 'hands' in pose_data and pose_data['hands']:
287
+ hands = pose_data['hands']
288
+ if len(hands) > 0:
289
+ person_data["hand_left_keypoints_2d"] = hands[0] if hands[0] else []
290
+ if len(hands) > 1:
291
+ person_data["hand_right_keypoints_2d"] = hands[1] if hands[1] else []
292
+
293
+ # 顔データ
294
+ if 'faces' in pose_data and pose_data['faces']:
295
+ faces = pose_data['faces']
296
+ if len(faces) > 0:
297
+ person_data["face_keypoints_2d"] = faces[0] if faces[0] else []
298
+
299
+ # フレームデータの構築
300
+ frame_data = {
301
+ "people": [person_data],
302
+ "canvas_width": canvas_width,
303
+ "canvas_height": canvas_height
304
+ }
305
 
306
+ export_data.append(frame_data)
 
 
 
 
 
 
307
 
308
  json_str = json.dumps(export_data, indent=2, ensure_ascii=False)
309
  # 通知はapp.py側で行う(重複回避)