| | import gradio as gr |
| | import os |
| | from PIL import Image |
| | from utils.pose_utils import initialize_dwpose, safe_detect_pose |
| | from utils.notifications import notify_success, notify_error, NotificationMessages |
| | from utils.coordinate_system import update_coordinate_system |
| | from utils.image_processing import process_uploaded_image |
| | from utils.export_utils import export_pose_as_image, export_pose_as_json, get_timestamp_filename |
| | from utils.model_setup import download_models as _download_models_setup |
| | import json |
| | import tempfile |
| | import base64 |
| | import io |
| | import time |
| |
|
| | |
| | _current_poses = None |
| | _current_frame_index = 0 |
| | _current_pose_data = None |
| | _is_updating = False |
| |
|
| | def load_javascript(): |
| | """JavaScriptファイルを読み込む""" |
| | try: |
| | js_path = os.path.join(os.path.dirname(__file__), "static", "pose_editor.js") |
| | with open(js_path, "r", encoding="utf-8") as f: |
| | js_content = f.read() |
| | return f"<script>{js_content}</script>" |
| | except FileNotFoundError: |
| | print(f"[ERROR] JavaScript file not found: {js_path}") |
| | return "<script>console.error('pose_editor.js not found');</script>" |
| |
|
| | def image_to_base64(image): |
| | """PIL画像をBase64データURLに変換""" |
| | if image is None: |
| | return None |
| | |
| | |
| | max_size = 640 |
| | image.thumbnail((max_size, max_size), Image.Resampling.LANCZOS) |
| | |
| | |
| | buffer = io.BytesIO() |
| | image.save(buffer, format='PNG') |
| | img_str = base64.b64encode(buffer.getvalue()).decode() |
| | return f"data:image/png;base64,{img_str}" |
| |
|
| | def main(): |
| | |
| | success, message = initialize_dwpose() |
| | if not success: |
| | print(f"警告: {message}") |
| | |
| | with gr.Blocks(title="Character OpenPose Editor", head=load_javascript()) as demo: |
| | gr.Markdown("# Character OpenPose Editor") |
| | gr.Markdown("2頭身・3頭身キャラクター向けのOpenPose・DWPoseのポーズ編集ツール") |
| | |
| | with gr.Row(): |
| | |
| | with gr.Column(scale=1): |
| | gr.Markdown("### 入力") |
| | |
| | |
| | input_image = gr.Image( |
| | label="参考画像", |
| | type="pil", |
| | elem_id="input_image" |
| | ) |
| | |
| | |
| | template_dropdown = gr.Dropdown( |
| | label="テンプレート", |
| | choices=["2頭身立ちポーズ", "3頭身立ちポーズ", "4頭身立ちポーズ", "5頭身立ちポーズ", "6頭身立ちポーズ", "7頭身立ちポーズ"], |
| | value="3頭身立ちポーズ" |
| | ) |
| | |
| | |
| | template_update_btn = gr.Button( |
| | "🔄 テンプレートに更新", |
| | variant="secondary" |
| | ) |
| | |
| | |
| | json_upload = gr.File( |
| | label="JSONファイルをロード", |
| | file_types=[".json"], |
| | elem_id="json_upload" |
| | ) |
| | |
| | |
| | with gr.Column(scale=2): |
| | gr.Markdown("### ポーズエディター") |
| | |
| | |
| | with gr.Row(equal_height=True): |
| | with gr.Column(scale=0, min_width=240): |
| | gr.Markdown("**表示設定**") |
| | with gr.Row(): |
| | draw_hand = gr.Checkbox(label="手を描画", value=True, container=False, scale=0, min_width=90) |
| | draw_face = gr.Checkbox(label="顔を描画", value=False, container=False, scale=0, min_width=90) |
| | with gr.Column(scale=1, min_width=160): |
| | gr.Markdown("**編集モード**") |
| | edit_mode = gr.Radio( |
| | choices=["簡易モード", "詳細モード"], |
| | value="簡易モード", |
| | container=False |
| | ) |
| | |
| | |
| | pose_canvas = gr.HTML( |
| | elem_id="pose_canvas_container", |
| | value='<canvas id="pose_canvas" width="640" height="640" style="border: 1px solid #ccc;"></canvas>' |
| | ) |
| | |
| | |
| | |
| | pose_data = gr.JSON(visible=False, value={}) |
| | |
| | |
| | js_pose_update = gr.Textbox(visible=False, elem_id="js_pose_update") |
| | |
| | |
| | with gr.Column(scale=1): |
| | gr.Markdown("### 出力") |
| | |
| | |
| | with gr.Row(): |
| | canvas_width = gr.Number( |
| | label="幅", |
| | value=512, |
| | minimum=64, |
| | maximum=2048, |
| | step=64, |
| | scale=1, |
| | min_width=100 |
| | ) |
| | canvas_height = gr.Number( |
| | label="高さ", |
| | value=512, |
| | minimum=64, |
| | maximum=2048, |
| | step=64, |
| | scale=1, |
| | min_width=100 |
| | ) |
| | |
| | canvas_update_btn = gr.Button( |
| | "画像サイズのUpdate", |
| | variant="secondary" |
| | ) |
| | |
| | |
| | output_image = gr.Image( |
| | label="ポーズ画像", |
| | type="pil", |
| | elem_id="output_image", |
| | visible=False |
| | ) |
| | |
| | |
| | download_image_btn = gr.Button( |
| | "📥 画像をダウンロード", |
| | variant="secondary" |
| | ) |
| | download_image_file = gr.File( |
| | label="画像ファイル", |
| | visible=False |
| | ) |
| | |
| | |
| | output_json = gr.JSON( |
| | label="ポーズデータ (JSON)", |
| | elem_id="output_json", |
| | visible=False |
| | ) |
| | |
| | |
| | download_json_btn = gr.Button( |
| | "📥 JSONをダウンロード", |
| | variant="secondary" |
| | ) |
| | download_json_file = gr.File( |
| | label="JSONファイル", |
| | visible=False |
| | ) |
| | |
| | |
| | def on_image_upload(image): |
| | """画像アップロード時のポーズ検出(refs互換・マルチフレーム管理)""" |
| | global _current_poses, _current_frame_index |
| | |
| | if image is None: |
| | |
| | |
| | return None, {}, gr.update(), gr.update(), gr.update() |
| | |
| | print(f"[DEBUG] 🖼️ Image upload detected: {type(image)}") |
| | |
| | |
| | processed_image, original_size, scale_info = process_uploaded_image(image) |
| | print(f"[DEBUG] 📐 Image processed: original_size={original_size}, scale_info={scale_info}") |
| | |
| | |
| | pose_result = safe_detect_pose(image) |
| | print(f"[DEBUG] 🤖 Pose detection result type: {type(pose_result)}") |
| | |
| | if pose_result is not None: |
| | print(f"[DEBUG] 📊 Pose result keys: {list(pose_result.keys()) if isinstance(pose_result, dict) else 'Not a dict'}") |
| | if isinstance(pose_result, dict) and 'bodies' in pose_result: |
| | bodies = pose_result['bodies'] |
| | if 'candidate' in bodies: |
| | candidates = bodies['candidate'] |
| | print(f"[DEBUG] 🎯 Candidates count: {len(candidates)}") |
| | print(f"[DEBUG] 📍 First 3 candidates: {candidates[:3] if len(candidates) >= 3 else candidates}") |
| | valid_count = len([c for c in candidates if c and len(c) >= 2 and c[0] > 0 and c[1] > 0]) |
| | zero_count = len([c for c in candidates if c and len(c) >= 2 and (c[0] == 0 or c[1] == 0)]) |
| | print(f"[DEBUG] ✅ Valid candidates: {valid_count}, 🚫 Zero coordinates: {zero_count}") |
| | |
| | |
| | if pose_result: |
| | |
| | person_data = { |
| | "pose_keypoints_2d": [], |
| | "hand_left_keypoints_2d": pose_result.get('hands', [[], []])[0] if pose_result.get('hands') else [], |
| | "hand_right_keypoints_2d": pose_result.get('hands', [[], []])[1] if pose_result.get('hands') and len(pose_result['hands']) > 1 else [], |
| | "face_keypoints_2d": pose_result.get('faces', [[]])[0] if pose_result.get('faces') else [] |
| | } |
| | |
| | |
| | if 'bodies' in pose_result and 'candidate' in pose_result['bodies']: |
| | candidates = pose_result['bodies']['candidate'] |
| | for candidate in candidates: |
| | if candidate and len(candidate) >= 2: |
| | person_data["pose_keypoints_2d"].extend([candidate[0], candidate[1], candidate[2] if len(candidate) > 2 else 1.0]) |
| | |
| | _current_poses = [{ |
| | 'people': [person_data], |
| | 'metadata': { |
| | 'resolution': pose_result.get('resolution', [512, 512]) |
| | } |
| | }] |
| | _current_frame_index = 0 |
| | print(f"[DEBUG] ✅ グローバル変数更新完了(画像アップロード・refs互換)") |
| | |
| | |
| | image_base64 = image_to_base64(image) |
| | pose_result['background_image'] = image_base64 |
| | |
| | |
| | try: |
| | |
| | data_w = int(original_size[0]) if original_size else 512 |
| | data_h = int(original_size[1]) if original_size else 512 |
| | |
| | new_w = max(64, min(2048, data_w)) |
| | new_h = max(64, min(2048, data_h)) |
| | |
| | |
| | except Exception: |
| | data_w, data_h = 512, 512 |
| | new_w = 512 |
| | new_h = 512 |
| |
|
| | |
| | js_code = f"setTimeout(() => updateCanvasResolution({new_w}, {new_h}), 100);" |
| | |
| | return ( |
| | pose_result, |
| | pose_result, |
| | gr.update(value=f"<script>{js_code}</script>"), |
| | gr.update(value=new_w), |
| | gr.update(value=new_h) |
| | ) |
| | else: |
| | print(f"[DEBUG] ❌ Pose detection failed") |
| | return None, {}, gr.update(), gr.update(), gr.update() |
| | |
| | def on_canvas_size_update(width, height): |
| | """Canvas解像度更新""" |
| | global _current_poses, _current_frame_index |
| | try: |
| | width = int(width) if width else 512 |
| | height = int(height) if height else 512 |
| | |
| | |
| | width = max(64, min(2048, width)) |
| | height = max(64, min(2048, height)) |
| | |
| | |
| | update_coordinate_system((width, height), (640, 640)) |
| | |
| | |
| | try: |
| | if _current_poses and 0 <= _current_frame_index < len(_current_poses): |
| | _current_poses[_current_frame_index]['metadata']['resolution'] = [width, height] |
| | except Exception: |
| | pass |
| |
|
| | |
| | js_code = f"updateCanvasResolution({width}, {height});" |
| | |
| | notify_success(f"Canvas解像度を{width}x{height}に更新しました") |
| | return gr.update(value=f"<script>{js_code}</script>") |
| | |
| | except Exception as e: |
| | notify_error(f"Canvas解像度更新に失敗しました: {str(e)}") |
| | return gr.update() |
| | |
| | def on_display_settings_change(draw_hand, draw_face, edit_mode): |
| | """表示設定変更時(JavaScript側で直接監視するため、現在は使用されていません)""" |
| | |
| | js_code = f""" |
| | if (window.updateDisplaySettings) {{ |
| | window.updateDisplaySettings({str(draw_hand).lower()}, {str(draw_face).lower()}, '{edit_mode}'); |
| | }} |
| | """ |
| | return gr.update(value=f"<script>{js_code}</script>") |
| | |
| | def load_template_pose(template_name): |
| | """テンプレートポーズを読み込み(refs互換・マルチフレーム管理)""" |
| | global _current_poses, _current_frame_index |
| | |
| | try: |
| | |
| | template_file_map = { |
| | "2頭身立ちポーズ": "2heads.json", |
| | "3頭身立ちポーズ": "3heads.json", |
| | "4頭身立ちポーズ": "4heads.json", |
| | "5頭身立ちポーズ": "5heads.json", |
| | "6頭身立ちポーズ": "6heads.json", |
| | "7頭身立ちポーズ": "7heads.json" |
| | } |
| | |
| | if template_name in template_file_map: |
| | templates_path = os.path.join(os.path.dirname(__file__), "poses", template_file_map[template_name]) |
| | with open(templates_path, "r", encoding="utf-8") as f: |
| | pose_data = json.load(f) |
| | else: |
| | |
| | templates_path = os.path.join(os.path.dirname(__file__), "templates", "poses.json") |
| | with open(templates_path, "r", encoding="utf-8") as f: |
| | templates = json.load(f) |
| | |
| | |
| | template_key_map = { |
| | "2頭身立ちポーズ": "2_head_standing", |
| | "2頭身座りポーズ": "2_head_sitting" |
| | } |
| | |
| | template_key = template_key_map.get(template_name) |
| | if template_key and template_key in templates["poses"]: |
| | pose_data = templates["poses"][template_key]["data"] |
| | else: |
| | notify_error("テンプレートが見つかりません") |
| | return None, {} |
| | |
| | if pose_data: |
| | |
| | if template_name in template_file_map: |
| | |
| | actual_pose_data = pose_data |
| | else: |
| | |
| | actual_pose_data = pose_data |
| | |
| | |
| | person_data = { |
| | "pose_keypoints_2d": [], |
| | "hand_left_keypoints_2d": actual_pose_data.get('hands', [[], []])[0] if actual_pose_data.get('hands') else [], |
| | "hand_right_keypoints_2d": actual_pose_data.get('hands', [[], []])[1] if actual_pose_data.get('hands') and len(actual_pose_data['hands']) > 1 else [], |
| | "face_keypoints_2d": actual_pose_data.get('faces', [[]])[0] if actual_pose_data.get('faces') else [] |
| | } |
| | |
| | |
| | if 'bodies' in actual_pose_data and 'candidate' in actual_pose_data['bodies']: |
| | candidates = actual_pose_data['bodies']['candidate'] |
| | for candidate in candidates: |
| | if candidate and len(candidate) >= 2: |
| | person_data["pose_keypoints_2d"].extend([candidate[0], candidate[1], candidate[2] if len(candidate) > 2 else 1.0]) |
| | |
| | _current_poses = [{ |
| | 'people': [person_data], |
| | 'metadata': { |
| | 'resolution': actual_pose_data.get('resolution', [512, 512]) |
| | } |
| | }] |
| | _current_frame_index = 0 |
| | |
| | people_format_data = { |
| | 'people': [person_data], |
| | 'bodies': actual_pose_data.get('bodies', {}), |
| | 'hands': actual_pose_data.get('hands', []), |
| | 'faces': actual_pose_data.get('faces', []), |
| | 'metadata': { |
| | 'resolution': actual_pose_data.get('resolution', [512, 512]) |
| | }, |
| | 'is_template_load': True |
| | } |
| | |
| | |
| | target_w, target_h = 512, 512 |
| | |
| | try: |
| | if _current_poses and 0 <= _current_frame_index < len(_current_poses): |
| | _current_poses[_current_frame_index]['metadata']['resolution'] = [target_w, target_h] |
| | except Exception: |
| | pass |
| |
|
| | |
| | js_code = f"setTimeout(() => updateCanvasResolution({target_w}, {target_h}), 100);" |
| |
|
| | notify_success(f"{template_name}を読み込みました") |
| | return ( |
| | people_format_data, |
| | people_format_data, |
| | gr.update(value=f"<script>{js_code}</script>"), |
| | gr.update(value=target_w), |
| | gr.update(value=target_h) |
| | ) |
| | else: |
| | notify_error("テンプレートが見つかりません") |
| | return None, {}, gr.update(), gr.update(), gr.update() |
| | |
| | except Exception as e: |
| | notify_error(f"テンプレート読み込みに失敗しました: {str(e)}") |
| | return None, {}, gr.update(), gr.update(), gr.update() |
| | |
| | def export_image(pose_data, draw_hand, draw_face, width, height): |
| | """ポーズ画像をエクスポート(Button + File方式)(refs互換・マルチフレーム管理)""" |
| | global _current_poses, _current_frame_index |
| | |
| | |
| | export_data = None |
| | if _current_poses and 0 <= _current_frame_index < len(_current_poses): |
| | current_frame = _current_poses[_current_frame_index] |
| | if current_frame['people'] and current_frame['people'][0]: |
| | person = current_frame['people'][0] |
| | |
| | |
| | export_data = { |
| | 'people': [person], |
| | 'resolution': current_frame['metadata'].get('resolution', [512, 512]) |
| | } |
| | |
| | |
| | if export_data is None: |
| | export_data = pose_data |
| | |
| | print(f"[DEBUG] 🖼️ 画像ダウンロードボタンクリック") |
| | print(f"[DEBUG] 📊 グローバルデータ: {bool(_current_poses)}, 引数データ: {bool(pose_data)}") |
| | print(f"[DEBUG] 📊 使用データ: {bool(export_data)}, 手: {draw_hand}, 顔: {draw_face}") |
| | |
| | if export_data: |
| | print(f"[DEBUG] 📊 エクスポートデータキー: {list(export_data.keys()) if isinstance(export_data, dict) else 'not a dict'}") |
| | if isinstance(export_data, dict): |
| | for key, value in export_data.items(): |
| | print(f"[DEBUG] 📊 {key}: {type(value)} - {len(value) if isinstance(value, (list, dict)) else value}") |
| | |
| | |
| | if _current_poses: |
| | print(f"[DEBUG] 🔍 グローバルデータ詳細:") |
| | print(f"[DEBUG] 🔍 - Type: {type(_current_poses)}") |
| | print(f"[DEBUG] 🔍 - Frames: {len(_current_poses) if _current_poses else 0}") |
| | if _current_poses and 0 <= _current_frame_index < len(_current_poses): |
| | current_frame = _current_poses[_current_frame_index] |
| | print(f"[DEBUG] 🔍 - Current frame: {bool(current_frame['people']) if 'people' in current_frame else False}") |
| | if current_frame.get('people'): |
| | person = current_frame['people'][0] |
| | print(f"[DEBUG] 🔍 - Person keys: {list(person.keys()) if person else 'None'}") |
| | else: |
| | print(f"[DEBUG] 🔍 グローバルデータがNone") |
| | |
| | if not export_data: |
| | notify_error("エクスポートするポーズデータがありません") |
| | print(f"[DEBUG] ❌ データなし") |
| | return gr.update(visible=False) |
| | |
| | try: |
| | |
| | canvas_width = int(width) if width else 512 |
| | canvas_height = int(height) if height else 512 |
| | |
| | |
| | canvas_width = max(64, min(2048, canvas_width)) |
| | canvas_height = max(64, min(2048, canvas_height)) |
| | |
| | |
| | print(f"[DEBUG] 🎨 画像生成開始: canvas_size=({canvas_width}, {canvas_height})") |
| | if 'bodies' in export_data and 'candidate' in export_data['bodies']: |
| | candidates = export_data['bodies']['candidate'] |
| | print(f"[DEBUG] 🎨 Candidates for image: {len(candidates) if candidates else 0}") |
| | if candidates: |
| | valid_candidates = [c for c in candidates if c and len(c) >= 2 and c[0] > 0 and c[1] > 0] |
| | print(f"[DEBUG] 🎨 Valid candidates: {len(valid_candidates)}") |
| | |
| | image = export_pose_as_image( |
| | export_data, |
| | canvas_size=(canvas_width, canvas_height), |
| | enable_hands=draw_hand, |
| | enable_face=draw_face |
| | ) |
| | if image is None: |
| | print(f"[DEBUG] ❌ 画像生成失敗 - export_pose_as_image returned None") |
| | return gr.update(visible=False) |
| | |
| | |
| | filename = get_timestamp_filename("dwpose_edit", "png") |
| | temp_path = os.path.join(tempfile.gettempdir(), filename) |
| | image.save(temp_path) |
| | |
| | print(f"[DEBUG] ✅ ファイル準備完了: {temp_path}") |
| | notify_success(f"画像をエクスポートしました: {filename}") |
| | |
| | |
| | return gr.update(value=temp_path, visible=True) |
| | |
| | except Exception as e: |
| | print(f"[DEBUG] ❌ エラー: {e}") |
| | notify_error(f"画像エクスポートエラー: {str(e)}") |
| | return gr.update(visible=False) |
| | |
| | def export_json(pose_data): |
| | """ポーズJSONをエクスポート(people形式・refs互換・グローバル管理)""" |
| | global _current_poses, _current_frame_index |
| | |
| | |
| | export_data = None |
| | if _current_poses and 0 <= _current_frame_index < len(_current_poses): |
| | current_frame = _current_poses[_current_frame_index] |
| | if current_frame['people'] and current_frame['people'][0]: |
| | |
| | export_data = { |
| | 'people': current_frame['people'], |
| | 'resolution': current_frame['metadata'].get('resolution', [512, 512]) |
| | } |
| | |
| | |
| | if export_data is None: |
| | export_data = pose_data |
| | |
| | print(f"[DEBUG] 📥 JSONダウンロードボタンクリック(people形式)") |
| | print(f"[DEBUG] 📊 グローバルデータ: {bool(_current_poses)}, 引数データ: {bool(pose_data)}") |
| | print(f"[DEBUG] 📊 使用データ: {bool(export_data)}") |
| | |
| | if not export_data: |
| | notify_error("エクスポートするポーズデータがありません") |
| | print(f"[DEBUG] ❌ データなし") |
| | return gr.update(visible=False) |
| | |
| | try: |
| | |
| | json_str = export_pose_as_json(export_data) |
| | if not json_str: |
| | print(f"[DEBUG] ❌ JSON生成失敗") |
| | return gr.update(visible=False) |
| | |
| | |
| | filename = get_timestamp_filename("dwpose_data", "json") |
| | temp_path = os.path.join(tempfile.gettempdir(), filename) |
| | |
| | with open(temp_path, 'w', encoding='utf-8') as f: |
| | f.write(json_str) |
| | |
| | print(f"[DEBUG] ✅ ファイル準備完了: {temp_path}") |
| | notify_success(f"people形式JSONをエクスポートしました: {filename}") |
| | |
| | |
| | return gr.update(value=temp_path, visible=True) |
| | |
| | except Exception as e: |
| | print(f"[DEBUG] ❌ エラー: {e}") |
| | notify_error(f"JSONエクスポートエラー: {str(e)}") |
| | return gr.update(visible=False) |
| | |
| | def on_js_pose_update(js_pose_str): |
| | """JavaScript側からのポーズデータ更新(refs互換・マルチフレーム管理)""" |
| | global _current_poses, _current_frame_index, _is_updating |
| | |
| | update_timestamp = int(time.time() * 1000) |
| | print(f"[DEBUG] 🔄 JavaScript→Python データ転送開始: {len(js_pose_str) if js_pose_str else 0}文字, timestamp={update_timestamp}") |
| | print(f"[DEBUG] 🔍 更新フラグ状態: _is_updating={_is_updating}") |
| | print(f"[DEBUG] 🔍 グローバル状態: _current_poses={bool(_current_poses)}, _current_frame_index={_current_frame_index}") |
| | |
| | |
| | if js_pose_str: |
| | try: |
| | data_preview = json.loads(js_pose_str) |
| | print(f"[DEBUG] 🔍 受信データ構造:", { |
| | 'hasPeople': 'people' in data_preview, |
| | 'peopleCount': len(data_preview.get('people', [])), |
| | 'hasHandLeft': bool(data_preview.get('people', [{}])[0].get('hand_left_keypoints_2d')) if data_preview.get('people') else False, |
| | 'hasHandRight': bool(data_preview.get('people', [{}])[0].get('hand_right_keypoints_2d')) if data_preview.get('people') else False, |
| | 'hasFace': bool(data_preview.get('people', [{}])[0].get('face_keypoints_2d')) if data_preview.get('people') else False |
| | }) |
| | except: |
| | print(f"[DEBUG] 🔍 受信データパース失敗") |
| | |
| | |
| | if _is_updating: |
| | print(f"[DEBUG] ⚠️ データ更新処理中のため、新しい要求をスキップ (timestamp={update_timestamp})") |
| | return gr.update(), "" |
| | |
| | if not js_pose_str or js_pose_str.strip() == "": |
| | return gr.update(), "" |
| | |
| | |
| | _is_updating = True |
| | print(f"[DEBUG] 🔒 データ更新フラグ設定: _is_updating={_is_updating} (timestamp={update_timestamp})") |
| | |
| | try: |
| | |
| | canvas_data = json.loads(js_pose_str) |
| | print(f"[DEBUG] 🎨 Canvas JSON解析成功: keys={list(canvas_data.keys())}") |
| | |
| | |
| | if '_t' in canvas_data: |
| | del canvas_data['_t'] |
| | print(f"[DEBUG] 🔄 タイムスタンプ除去完了") |
| | |
| | |
| | if 'people' in canvas_data and canvas_data['people']: |
| | pose_data = canvas_data['people'][0] |
| | print(f"[DEBUG] 🎯 People形式データ抽出成功") |
| | |
| | |
| | if _current_poses is None: |
| | _current_poses = [{ |
| | 'people': [pose_data], |
| | 'metadata': { |
| | 'resolution': canvas_data.get('resolution', [512, 512]) |
| | } |
| | }] |
| | print(f"[DEBUG] 🚀 _current_poses初期化完了") |
| | else: |
| | |
| | if 0 <= _current_frame_index < len(_current_poses): |
| | _current_poses[_current_frame_index]['people'] = [pose_data] |
| | |
| | try: |
| | if 'resolution' in canvas_data and isinstance(canvas_data['resolution'], list): |
| | _current_poses[_current_frame_index]['metadata']['resolution'] = canvas_data['resolution'] |
| | except Exception: |
| | pass |
| | print(f"[DEBUG] 🎯 フレーム{_current_frame_index}のpeopleデータ更新完了") |
| | else: |
| | |
| | _current_poses.append({ |
| | 'people': [pose_data], |
| | 'metadata': { |
| | 'resolution': canvas_data.get('resolution', [512, 512]) |
| | } |
| | }) |
| | print(f"[DEBUG] 🎯 新フレーム追加完了") |
| | |
| | |
| | current_frame_data = _current_poses[_current_frame_index] |
| | display_data = { |
| | 'bodies': {'candidate': [], 'subset': []}, |
| | 'hands': [], |
| | 'faces': [], |
| | 'resolution': current_frame_data['metadata'].get('resolution', [512, 512]) |
| | } |
| | |
| | |
| | if current_frame_data['people'] and current_frame_data['people'][0]: |
| | person = current_frame_data['people'][0] |
| | |
| | |
| | if 'pose_keypoints_2d' in person: |
| | pose_keypoints = person['pose_keypoints_2d'] |
| | print(f"[DEBUG] 🔄 pose_keypoints length: {len(pose_keypoints)}") |
| | |
| | if len(pose_keypoints) >= 3: |
| | for i in range(0, len(pose_keypoints), 3): |
| | if i + 2 < len(pose_keypoints): |
| | x = pose_keypoints[i] |
| | y = pose_keypoints[i + 1] |
| | conf = pose_keypoints[i + 2] |
| | display_data['bodies']['candidate'].append([x, y, conf, 0]) |
| | |
| | |
| | left_hand = person.get('hand_left_keypoints_2d', []) |
| | right_hand = person.get('hand_right_keypoints_2d', []) |
| | face_data = person.get('face_keypoints_2d', []) |
| | |
| | display_data['hands'] = [left_hand, right_hand] |
| | display_data['faces'] = [face_data] if face_data else [] |
| | |
| | print(f"[DEBUG] 🫳 Hand data: left={len(left_hand)}, right={len(right_hand)}") |
| | print(f"[DEBUG] 😊 Face data: {len(face_data) if face_data else 0}") |
| | |
| | print(f"[DEBUG] ✅ refs互換データ更新完了: frames={len(_current_poses)}") |
| | print(f"[DEBUG] 📊 表示用データ: candidates={len(display_data['bodies']['candidate'])}") |
| | |
| | |
| | return display_data, "" |
| | |
| | except json.JSONDecodeError as e: |
| | print(f"[DEBUG] ❌ JavaScript→Python JSONパースエラー: {e}") |
| | return gr.update(), "" |
| | except Exception as e: |
| | print(f"[DEBUG] ❌ JavaScript→Python エラー: {e}") |
| | return gr.update(), "" |
| | finally: |
| | |
| | old_flag = _is_updating |
| | _is_updating = False |
| | print(f"[DEBUG] 🔓 データ更新処理完了 - フラグ解除: {old_flag} → {_is_updating} (timestamp={update_timestamp})") |
| | |
| | def on_json_upload(file): |
| | """JSONファイルアップロード時の処理(people形式対応・refs互換)""" |
| | global _current_poses, _current_frame_index |
| | |
| | if file is None: |
| | return None, {}, gr.update(), gr.update(), gr.update() |
| | |
| | try: |
| | |
| | with open(file.name, 'r', encoding='utf-8') as f: |
| | json_content = f.read() |
| | |
| | |
| | loaded_data = json.loads(json_content) |
| | |
| | |
| | if isinstance(loaded_data, list) and len(loaded_data) > 0: |
| | |
| | frame_data = loaded_data[0] |
| | |
| | if 'people' in frame_data and frame_data['people']: |
| | person_data = frame_data['people'][0] |
| | canvas_width = frame_data.get('canvas_width', 512) |
| | canvas_height = frame_data.get('canvas_height', 512) |
| | |
| | |
| | _current_poses = [{ |
| | 'people': [person_data], |
| | 'metadata': { |
| | 'resolution': [canvas_width, canvas_height] |
| | } |
| | }] |
| | _current_frame_index = 0 |
| | |
| | |
| | bodies_data = convert_people_to_bodies_format(person_data, [canvas_width, canvas_height]) |
| | display_data = { |
| | 'people': [person_data], |
| | 'bodies': bodies_data['bodies'], |
| | 'hands': bodies_data['hands'], |
| | 'faces': bodies_data['faces'], |
| | 'resolution': [canvas_width, canvas_height], |
| | 'metadata': {'resolution': [canvas_width, canvas_height]} |
| | } |
| | |
| | print(f"[DEBUG] ✅ people形式JSON読み込み完了(ハイブリッド形式)") |
| | notify_success("people形式JSONファイルを読み込みました") |
| | |
| | js_code = f"setTimeout(() => updateCanvasResolution({int(canvas_width)}, {int(canvas_height)}), 100);" |
| | return ( |
| | display_data, |
| | display_data, |
| | gr.update(value=f"<script>{js_code}</script>"), |
| | gr.update(value=int(canvas_width)), |
| | gr.update(value=int(canvas_height)) |
| | ) |
| | else: |
| | notify_error("無効なpeople形式データです") |
| | return None, {}, gr.update(), gr.update(), gr.update() |
| | |
| | else: |
| | |
| | if not validate_pose_json(loaded_data): |
| | notify_error("無効なポーズデータフォーマットです") |
| | return None, {} |
| | |
| | |
| | if 'metadata' in loaded_data: |
| | del loaded_data['metadata'] |
| | |
| | |
| | person_data = { |
| | "pose_keypoints_2d": [], |
| | "hand_left_keypoints_2d": loaded_data.get('hands', [[], []])[0] if loaded_data.get('hands') else [], |
| | "hand_right_keypoints_2d": loaded_data.get('hands', [[], []])[1] if loaded_data.get('hands') and len(loaded_data['hands']) > 1 else [], |
| | "face_keypoints_2d": loaded_data.get('faces', [[]])[0] if loaded_data.get('faces') else [] |
| | } |
| | |
| | |
| | if 'bodies' in loaded_data and 'candidate' in loaded_data['bodies']: |
| | candidates = loaded_data['bodies']['candidate'] |
| | for candidate in candidates: |
| | if candidate and len(candidate) >= 2: |
| | person_data["pose_keypoints_2d"].extend([candidate[0], candidate[1], candidate[2] if len(candidate) > 2 else 1.0]) |
| | |
| | _current_poses = [{ |
| | 'people': [person_data], |
| | 'metadata': { |
| | 'resolution': loaded_data.get('resolution', [512, 512]) |
| | } |
| | }] |
| | _current_frame_index = 0 |
| | |
| | |
| | hybrid_data = loaded_data.copy() |
| | hybrid_data['people'] = [person_data] |
| | |
| | print(f"[DEBUG] ✅ bodies形式JSON読み込み・変換完了(ハイブリッド形式)") |
| | notify_success("bodies形式JSONファイルを読み込みました(people形式に変換)") |
| | |
| | res = loaded_data.get('resolution', [512, 512]) |
| | try: |
| | w = int(res[0]); h = int(res[1]) |
| | except Exception: |
| | w, h = 512, 512 |
| | js_code = f"setTimeout(() => updateCanvasResolution({w}, {h}), 100);" |
| | return ( |
| | hybrid_data, |
| | hybrid_data, |
| | gr.update(value=f"<script>{js_code}</script>"), |
| | gr.update(value=w), |
| | gr.update(value=h) |
| | ) |
| | |
| | except json.JSONDecodeError as e: |
| | notify_error(f"JSONパースエラー: {str(e)}") |
| | return None, {}, gr.update(), gr.update(), gr.update() |
| | except Exception as e: |
| | notify_error(f"ファイル読み込みエラー: {str(e)}") |
| | return None, {}, gr.update(), gr.update(), gr.update() |
| | |
| | def convert_people_to_bodies_format(person_data, resolution): |
| | """people形式からbodies形式に変換(表示互換性用)""" |
| | bodies_data = { |
| | 'bodies': {'candidate': [], 'subset': []}, |
| | 'hands': [], |
| | 'faces': [], |
| | 'resolution': resolution |
| | } |
| | |
| | |
| | if 'pose_keypoints_2d' in person_data: |
| | pose_keypoints = person_data['pose_keypoints_2d'] |
| | for i in range(0, len(pose_keypoints), 3): |
| | if i + 2 < len(pose_keypoints): |
| | x = pose_keypoints[i] |
| | y = pose_keypoints[i + 1] |
| | conf = pose_keypoints[i + 2] |
| | bodies_data['bodies']['candidate'].append([x, y, conf, 0]) |
| | |
| | |
| | left_hand = person_data.get('hand_left_keypoints_2d', []) |
| | right_hand = person_data.get('hand_right_keypoints_2d', []) |
| | face_data = person_data.get('face_keypoints_2d', []) |
| | |
| | bodies_data['hands'] = [left_hand, right_hand] |
| | bodies_data['faces'] = [face_data] if face_data else [] |
| | |
| | return bodies_data |
| | |
| | def validate_pose_json(data): |
| | """ポーズJSONデータの検証""" |
| | if not isinstance(data, dict): |
| | return False |
| | |
| | |
| | required_keys = ['bodies'] |
| | for key in required_keys: |
| | if key not in data: |
| | return False |
| | |
| | |
| | if 'bodies' in data: |
| | bodies = data['bodies'] |
| | if not isinstance(bodies, dict): |
| | return False |
| | if 'candidate' not in bodies and 'subset' not in bodies: |
| | return False |
| | |
| | return True |
| | |
| | |
| | js_executor = gr.HTML(visible=False, elem_id="js_executor") |
| | |
| | |
| | input_image.change( |
| | fn=on_image_upload, |
| | inputs=[input_image], |
| | outputs=[output_json, pose_data, js_executor, canvas_width, canvas_height] |
| | ) |
| | |
| | |
| | pose_data.change( |
| | fn=None, |
| | inputs=pose_data, |
| | outputs=[], |
| | js="(pose_data) => { if (window.gradioCanvasUpdate) { window.gradioCanvasUpdate(JSON.stringify(pose_data)); } }" |
| | ) |
| | |
| | |
| | |
| | draw_hand.change( |
| | fn=on_display_settings_change, |
| | inputs=[draw_hand, draw_face, edit_mode], |
| | outputs=[js_executor] |
| | ) |
| | |
| | draw_face.change( |
| | fn=on_display_settings_change, |
| | inputs=[draw_hand, draw_face, edit_mode], |
| | outputs=[js_executor] |
| | ) |
| | |
| | edit_mode.change( |
| | fn=on_display_settings_change, |
| | inputs=[draw_hand, draw_face, edit_mode], |
| | outputs=[js_executor] |
| | ) |
| | |
| | |
| | template_update_btn.click( |
| | fn=load_template_pose, |
| | inputs=[template_dropdown], |
| | outputs=[output_json, pose_data, js_executor, canvas_width, canvas_height] |
| | ) |
| | |
| | |
| | download_image_btn.click( |
| | fn=export_image, |
| | inputs=[pose_data, draw_hand, draw_face, canvas_width, canvas_height], |
| | outputs=[download_image_file] |
| | ) |
| | |
| | download_json_btn.click( |
| | fn=export_json, |
| | inputs=[pose_data], |
| | outputs=[download_json_file] |
| | ) |
| | |
| | |
| | js_pose_update.change( |
| | fn=on_js_pose_update, |
| | inputs=[js_pose_update], |
| | outputs=[pose_data, js_pose_update] |
| | ) |
| | |
| | |
| | json_upload.change( |
| | fn=on_json_upload, |
| | inputs=[json_upload], |
| | outputs=[output_json, pose_data, js_executor, canvas_width, canvas_height] |
| | ) |
| |
|
| | |
| | canvas_update_btn.click( |
| | fn=on_canvas_size_update, |
| | inputs=[canvas_width, canvas_height], |
| | outputs=[js_executor] |
| | ) |
| | |
| | return demo |
| |
|
| | if __name__ == "__main__": |
| | import argparse, sys |
| | parser = argparse.ArgumentParser() |
| | parser.add_argument("--setup-models", action="store_true", help="Download required models into ./models and exit") |
| | args, _ = parser.parse_known_args() |
| |
|
| | if args.setup_models: |
| | code = _download_models_setup() |
| | |
| | sys.exit(code) |
| | else: |
| | demo = main() |
| | demo.launch() |
| |
|