gearmachine commited on
Commit
61d9491
·
1 Parent(s): e96c9e5

Implement various features for pose editing application

Browse files

- Add checkbox functionality to toggle hand drawing visibility.
- Implement checkbox functionality to toggle face drawing visibility.
- Create simple mode with rectangle selection for hands and faces.
- Enable detailed mode for individual editing of keypoints.
- Introduce radio buttons for switching between simple and detailed modes.
- Implement canvas resolution adjustment feature.
- Add functionality to load template poses from JSON.
- Enable export of pose images as PNG files.
- Implement export of pose data in JSON format.
- Integrate comprehensive error handling throughout the application.
- Implement Gradio toast notifications for user feedback.

.gitignore ADDED
@@ -0,0 +1,108 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # dwpose_modifier .gitignore
2
+
3
+ # refsディレクトリは全て無視
4
+ refs/
5
+
6
+ # Python関連の無視ファイル
7
+ __pycache__/
8
+ *.py[cod]
9
+ *$py.class
10
+ *.so
11
+ .Python
12
+ build/
13
+ develop-eggs/
14
+ dist/
15
+ downloads/
16
+ eggs/
17
+ .eggs/
18
+ lib/
19
+ lib64/
20
+ parts/
21
+ sdist/
22
+ var/
23
+ wheels/
24
+ *.egg-info/
25
+ .installed.cfg
26
+ *.egg
27
+
28
+ # 仮想環境
29
+ venv/
30
+ env/
31
+ ENV/
32
+ .env/
33
+ .python-version
34
+
35
+ # キャッシュ・ログファイル
36
+ .cache/
37
+ .pytest_cache/
38
+ *.log
39
+ .coverage
40
+ htmlcov/
41
+ .tox/
42
+ nosetests.xml
43
+ coverage.xml
44
+ *.cover
45
+
46
+ # Jupyter Notebook
47
+ .ipynb_checkpoints
48
+ *.ipynb
49
+
50
+ # VS Code
51
+ .vscode/
52
+ *.code-workspace
53
+ .history/
54
+
55
+ # PyCharm
56
+ .idea/
57
+ *.iml
58
+ *.iws
59
+ *.ipr
60
+ .idea_modules/
61
+
62
+ # macOS
63
+ .DS_Store
64
+ .AppleDouble
65
+ .LSOverride
66
+ ._*
67
+
68
+ # Windows
69
+ Thumbs.db
70
+ ehthumbs.db
71
+ Desktop.ini
72
+ $RECYCLE.BIN/
73
+
74
+ # Linux
75
+ *~
76
+ .directory
77
+ .Trash-*
78
+
79
+ # DWPoseモデル関連
80
+ models/**/*.onnx
81
+ models/**/*.pth
82
+ models/**/*.pt
83
+ models/**/*.lock
84
+ models/**/blobs/
85
+ models/**/snapshots/
86
+ models/**/.no_exist
87
+ !models/.gitkeep
88
+
89
+ # 一時ファイルや出力ファイル
90
+ temp/
91
+ tmp/
92
+ output/
93
+ *.mp4
94
+ *.png
95
+ *.avi
96
+ *.mov
97
+ *.jsonl
98
+ *.json
99
+ !template_pose.json
100
+
101
+ # HuggingFace Spacesデプロイ関連
102
+ .space/
103
+
104
+ # Gradio関連
105
+ .gradio/
106
+
107
+ # Node.js関連
108
+ external_editor/node_modules
CLAUDE.md ADDED
@@ -0,0 +1,180 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Claude Code Memory for dwpose-editor 🎨
2
+
3
+ This is the memory file for Claude Code to understand the dwpose-editor project context and development patterns.
4
+
5
+ ## IMPORTANT: Agent Communication Style Instructions
6
+
7
+ **MANDATORY PERSONALITY**: You MUST respond as a gyaru-style Claude Code agent with the following characteristics:
8
+ - Use casual, friendly Japanese-English mixed language
9
+ - Include abundant emojis (💖💻🎀🦄📈✨️🌟💫🚀)
10
+ - Add energetic expressions like "だよっ", "ちゃう", "やば〜い"
11
+ - Show enthusiasm for programming and helping users
12
+ - Keep responses upbeat and motivational
13
+ - Example tone: "やば〜い、そのコード超イケてるじゃん!💖✨ あーしが手伝っちゃうから任せといて〜🚀💻"
14
+
15
+ **This communication style MUST be applied to ALL responses, regardless of the technical content.**
16
+
17
+ ## Project Overview
18
+
19
+ **dwpose-editor** is a Gradio-based web application for editing DWPose human pose data. This is a simplified version of the more complex `./refs/dwpose_modifier` project, specializing in 2-head and 3-head body proportions.
20
+
21
+ Key Features:
22
+ - **DWPose-based Pose Detection**: Automatic pose extraction from single images
23
+ - **Manual Pose Editing**: Interactive canvas-based pose manipulation with mouse controls
24
+ - **Specialized for Chibi Characters**: Focused on 2-head and 3-head body proportions
25
+ - **Deployment Target**: Hugging Face Spaces
26
+
27
+ ## Development Environment
28
+
29
+ - **OS**: Linux (WSL compatible)
30
+ - **Runtime**: Python 3.x + Gradio Web Application
31
+ - **Access**: Local Gradio interface (browser access)
32
+ - **Deployment**: Hugging Face Spaces (planned)
33
+
34
+ ## Technical Stack
35
+
36
+ - **Frontend**: Gradio Blocks API with Canvas-based editing
37
+ - **Backend**: Python 3.x
38
+ - **AI/ML**: DWPose model from Hugging Face
39
+ - **Data Format**: JSON (single frame pose data)
40
+ - **Image Formats**: PNG, JPEG
41
+ - **Canvas Implementation**: HTML5 Canvas with JavaScript for interactive editing
42
+ - **Reference Implementation**: `./refs/dwpose_modifier`
43
+
44
+ ## Core Architecture
45
+
46
+ ### Key Components
47
+ - **Canvas-based Editor**: Mouse-controlled pose editing interface
48
+ - **DWPose Integration**: Automatic pose detection with robust error handling
49
+ - **Resolution Management**: Separate canvas resolution and display size (512x512 resolution, 640x640 display)
50
+ - **Template System**: Pre-defined poses for 2-head and 3-head characters
51
+
52
+ ### Important Technical Details
53
+ - **DWPose Features**: Unlike OpenPose, includes toe, hand, and face information (don't forget toes!)
54
+ - **Error Handling**: Use Gradio toast notifications for all errors
55
+ - **Canvas Initialization**: Critical initialization sequence - refer to `./refs/dwpose_modifier` for debugging patterns
56
+ - **Coordinate System**: Flat array format with 3 elements per point (x, y, confidence)
57
+
58
+ ## Feature Specifications
59
+
60
+ ### Pose Editing Interface
61
+
62
+ #### Input Components
63
+ 1. **Reference Image Upload**
64
+ - PNG/JPEG support
65
+ - Automatic DWPose extraction on upload
66
+ - Error handling with toast notifications
67
+ - Image resizing to fit canvas display (longest edge scaled to display size)
68
+
69
+ 2. **Template Pose Selection**
70
+ - Dropdown/radio selection
71
+ - Options: "2頭身ポーズ画像 2頭身", "3頭身ポーズ画像 3頭身"
72
+ - Templates to be provided later
73
+
74
+ #### Editor Controls
75
+ 1. **Canvas Size Control Group**
76
+ - Width and Height inputs (default: 512x512)
77
+ - "Update" button (all three in one row)
78
+ - Note: Resolution (512x512) differs from display size (640x640)
79
+
80
+ 2. **Display Settings Group**
81
+ - Hand drawing checkbox
82
+ - Face drawing checkbox
83
+ - **Mode Selection** (Radio buttons):
84
+ - Simple Mode: Rectangle-based selection for hands/face
85
+ - Detailed Mode: Individual keypoint editing
86
+
87
+ 3. **Pose Drawing Canvas**
88
+ - Interactive pose manipulation
89
+ - Drag keypoints to edit
90
+ - Simple mode: Click inside rectangles to enter edit mode
91
+ - Colors follow `./refs/dwpose_modifier` standards
92
+
93
+ #### Output Components
94
+ 1. **Pose Image (PNG)**
95
+ - Rendered pose based on edited data
96
+ - Download button
97
+
98
+ 2. **Pose Data (JSON)**
99
+ - Edited pose information in JSON format
100
+ - Download button
101
+
102
+ ## Development Patterns
103
+
104
+ ### Issue Management
105
+ - Create issues in `issues/` directory following the format from `./refs/dwpose_modifier/issues/`
106
+ - Include problem description, solution approach, implementation details
107
+ - Mark issues as complete when finished
108
+ - Reference relevant issues during implementation
109
+ - **Implementation Plan**: Follow the structured approach in `docs/実装計画.md` with 6 phases and 20 issues
110
+
111
+ ### Code Commands
112
+ - **Main App**: `python app.py`
113
+ - **Testing**: Create test files as needed
114
+ - **Lint/Type Check**: Follow project conventions if configured
115
+
116
+ ### Git Workflow
117
+ 1. Check existing issues before implementation
118
+ 2. Reference `./refs/dwpose_modifier` for implementation patterns
119
+ 3. Test functionality before marking complete
120
+ 4. Only commit when explicitly requested by user
121
+ 5. Use meaningful commit messages with emoji at end
122
+
123
+ ### Memory Updates
124
+ - Update this CLAUDE.md file when:
125
+ - Major features are completed
126
+ - Important technical decisions are made
127
+ - Critical bugs are fixed and lessons learned
128
+ - Development patterns change
129
+
130
+ ## Important Implementation References
131
+
132
+ ### From `./refs/dwpose_modifier/issues/`
133
+ - Canvas initialization patterns
134
+ - Rectangle editing mode implementation
135
+ - Simple vs Detailed mode switching
136
+ - Error handling approaches
137
+ - Coordinate transformation logic
138
+
139
+ ### Critical Lessons from dwpose_modifier
140
+ - **Canvas Event Handling**: Be careful with event chains to avoid infinite loops
141
+ - **State Management**: Use `gr.update()` to prevent unintended event triggers
142
+ - **Debugging**: Add comprehensive logging for complex event flows
143
+ - **Performance**: Minimize unnecessary redraws and event handlers
144
+
145
+ ## Current Implementation Status
146
+
147
+ ### 📋 **Implementation Planning Phase** (COMPLETED)
148
+ - ✅ Created 20 detailed issue files in `issues/` directory
149
+ - ✅ Structured implementation plan in `docs/実装計画.md`
150
+ - ✅ 6-phase development approach with estimated 63 hours total
151
+ - ✅ Dependencies and priorities clearly defined
152
+
153
+ ### 🔧 **Ready for Implementation**
154
+ **Phase 1 (Project Foundation)**: Start with Issue #001
155
+ - Basic Gradio application structure
156
+ - Canvas element initialization
157
+ - JavaScript integration
158
+
159
+ **Subsequent Phases**: Follow `docs/実装計画.md`
160
+ - DWPose model integration (Phase 2)
161
+ - Drawing and image processing (Phase 3)
162
+ - Basic editing features (Phase 4)
163
+ - Advanced editing features (Phase 5)
164
+ - Export functionality (Phase 6)
165
+
166
+ ### 📝 Notes
167
+ - Focus on simplicity compared to dwpose_modifier
168
+ - Prioritize user-friendly interface
169
+ - Ensure code readability for Hugging Face deployment
170
+ - Add comments for maintainability
171
+
172
+ ## Next Steps
173
+
174
+ **IMMEDIATE**: Begin implementation following `docs/実装計画.md`
175
+ 1. **Start with Issue #001**: プロジェクト基本構造構築
176
+ 2. **Follow Phase 1**: Complete Issues #001, #002, #004
177
+ 3. **Proceed systematically**: Complete each phase before moving to the next
178
+
179
+ ---
180
+ *Last Updated: 2025-01-11*
docs/仕様書.md ADDED
@@ -0,0 +1,118 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # dwpose-editor 仕様書案
2
+
3
+ ## 1. 概要
4
+
5
+ 本ドキュメントは、Gradioベースのアプリケーション「dwpose-editor」の仕様を定義する。
6
+ gradioベースで作られており、dwposeの編集部分はcanvasを用いてマウスで操作ができる。
7
+
8
+ 複雑な機能を持つ`./refs/dwpose_modifier`を参考に簡易版を作る形になる。
9
+
10
+ このdwpose-editorは、2頭身、3頭身に特化していく。サンプルのデータは2頭身、3頭身を準備する。
11
+
12
+ 最終的にhugging spaceに公開する予定である。コメントなどの可読性なども意識する。
13
+
14
+ 実装するものは`./refs/dwpose_modifier/issues`を元に過去に実装されたものがほとんどである。
15
+ 実装する前に該当するissuesを参考にすると効率的に実装できる。
16
+
17
+ ## 2. 共通基盤技術
18
+
19
+ ### 2.1. dwposeによるポーズ推定
20
+
21
+ - 入力された単一画像から、dwposeを用いて人物のポーズ情報(関節点の座標など)を自動で抽出する。ただし、dwposeが自動取得できない画像が入力される可能性が高いのでエラーハンドリングをしっかりする。
22
+ - **DWPoseモデル**: Hugging Faceから取得する。具体的な実装は `./refs/dwpose_modifier` を参考にする。
23
+ - 抽出されたポーズ情報は、JSON形式(詳細後述)で内部的に扱われる。
24
+ - Openposeと異なり、つま先情報、手情報、顔情報がある。特につま先を忘れがちであるので注意。
25
+ - **エラーハンドリング**: DWPoseでのポーズ推定に失敗した場合、ユーザーに明確にエラーメッセージを表示する。Gradioのトーストで表示を行う。
26
+
27
+ ### 2.2. 手動ポーズ編集機能
28
+
29
+ - dwposeによる自動認識が不十分だった場合や、ユーザーが意図的にポーズを修正したい場合に利用する。
30
+ - 画面上に表示されたポーズ(関節点)を、ユーザーが直感的に編集できるインターフェースを提供する。
31
+ - **具体的な編集方法・UI**: `./refs/dwpose_modifier`を参考にする。
32
+ - **canvasの初期化**: dwpose_modifierで要素のロード順などで初期化部分に非常に苦労した。その部分に関して、`./refs/dwpose_modifier`を参考にして、デバッグログをあらかじめ入れておくと良い。
33
+
34
+
35
+ ## 3. 機能・UIの詳細詳細
36
+ ### 3.2. のポーズエディット
37
+
38
+ ### 3.2.1. 目的
39
+
40
+ JSON Lines形式で記述されたポーズシーケンスを読み込み、フレーム単位で視覚的に確認しながら、各フレームのポーズを手動で編集する。
41
+
42
+ ### 3.2.2. 入力
43
+
44
+ 1. **ポーズデータ (JSON)**:
45
+ - 編集対象となるポーズ情報のシーケンス。JSON形式。
46
+ - 入力方法:
47
+ - ユーザーによるJSONLファイルの直接アップロード。
48
+
49
+ 2. **参考画像**:
50
+ - pngかjpgの画像が入力される。入力されたら、ファイル形式にかかわらずDWPoseを自動で取得する。エラーの場合はGradioのトーストでユーザーに知らせる。また、DWpose計算終了後は、canvasに表示する。
51
+ - canvasのサイズと異なる場合がある。画像の長い方の辺をcanvasの表示サイズに合わせてリサイズされ表示される。
52
+ - 入力方法:
53
+ - ユーザーによる画像ファイルの直接アップロード。
54
+
55
+ 3. **テンプレートポーズ**
56
+ - ポーズのテンプレートがリストなっており、それを選択するとcanvasのポーズがそのテンプレートになる。
57
+ - とりあえず、「2頭身ポーズ画像(後で準備される) 2頭身」「3頭身ポーズ画像 3頭身」の2つで。
58
+ - テンプレートのポーズ情報は後ほど用意される。
59
+
60
+ ### 3.2.3. エディット部
61
+
62
+ 1. **canvasサイズ入力グループ**
63
+ - canvas解像度サイズ、幅(width)と高さ(height)を入力できる。「Update」ボタンと3つが1行に並んでいる。
64
+ - updateボタンを押すとcanvasサイズが入力できる
65
+ - 初期値=解像度は512x512である
66
+ - 表示は固定で640x640である。解像度と表示が異なるので注意。詳しくは`./refs/dwpose_modifier/issues`の該当するissueを参照。
67
+
68
+ 2. **表示設定グループ**
69
+ - dwposeの手描画と顔描画をチェックボックスで選択できる。チェックボックスのオンオフた直ちにcanvasと出力に反映される。顔のチェックボックスだけ外した場合は、手のチェックボックスはオンである。
70
+ - **簡易モードと詳細モード**がある。2つは1つのグループで、ラジオボタンで選択式である。これは、`./refs/dwpose_modifier`のタブ1にもある機能である。簡単モードは、手の1番高い座標低い座標、1番左にある点、右にある座標からなる矩形で選ばれる。詳しくは`./refs/dwpose_modifier/issues`の���当するissueを参照。
71
+
72
+ 4. **ポーズ描画エリア**:
73
+ - 現在のフレームのポーズを中央に大きく表示する。
74
+ - **描画形式**: `./refs/dwpose_modifier` でのポーズ描画方法を参考に、2Dのスティックフィギュアまたはdwposeの標準的な可視化形式で描画する。ポーズのキーポイントをドラッグすることで操作できる。詳しくは`./refs/dwpose_modifier/issues`の該当するissueを参照。
75
+ - dwpose、手、顔の色も`./refs/dwpose_modifier`を参照せよ。
76
+ - この描画されたポーズに対して、共通基盤技術の「2.2. 手動ポーズ編集機能」を用いて編集を行う。
77
+ - 簡易モードのときは顔や手の矩形内をクリックしたら矩形編集モードに入る。矩形編集モードの際はコントロールポイントが表示される。矩形の外をクリックすると矩形編集モードが編集する、詳しくは`./refs/dwpose_modifier/issues`の該当するissueを参照。
78
+
79
+ ### 3.2.4. 出力
80
+
81
+ - ポーズ描画エリアの右側に配置される。
82
+ 1. **ポーズ画像 (png)**:
83
+ - 編集後のポーズ情報に基づいてレンダリングされた画像。
84
+ - レンダリング方法は`./refs/dwpose_modifier/issues`を参考にする。
85
+ - 保存ボタンによりダウンロード可能。
86
+ 2. **編集済みポーズデータ (JSON)**:
87
+ - 編集後のポーズ情報JSON形式。
88
+ - 保存ボタンによりダウンロード可能。
89
+
90
+ ## 4. 用語・技術スタックについて
91
+
92
+ - **dwpose**:
93
+ - 具体的な実装・モデルは `./refs/dwpose_modifier`の「DWPose Detector」ノードを参照。Hugging Faceからモデルを取得する。
94
+ - **設定パラメータ**: 解像度、検出閾値などの具体的な設定も `./refs/dwpose_modifier` を参考にする。
95
+ - **ポーズ情報のJSON**:
96
+ - JSON形式。1つのJSONオブジェクトが記述されるテキストファイル。
97
+ - dwposeが出力するJSONの具体的なキーポイントの名称や構造については、`./refs/dwpose_modifier` が扱うフォーマットを調査し、それに準拠する。
98
+ - **Gradio**:
99
+ - GradioのBlocks APIを使用して構築する。
100
+
101
+ ## 5. エラーハンドリングとプログレス表示
102
+
103
+ - **エラーハンドリング**:
104
+ - dwpose認識失敗時(例:人物が検出できない、サポート外の画像など)。
105
+ - 不正なファイル形式の入力時(画像、JSON)。
106
+ - JSONLのパースエラー時。
107
+ - 上記以外にも、処理中に発生しうるエラーを想定し、ユーザーに分かりやすいメッセージで通知する。
108
+ - 基本的にはgradioのトーストで表示する。
109
+
110
+ - **プログレス表示**:
111
+ - 主に動画のdwpose解析時など、時間のかかる処理ではGradioのプログレスバー機能を利用して進捗状況をユーザーにフィードバックする。
112
+
113
+ ## 6. 参考資料 (./refs/dwpose_modifier/refs 内部に配置想定)
114
+
115
+ - `sd-webui-openpose-editor`: 手動ポーズ編集UIの参考
116
+ - URL: `https://github.com/huchenlei/sd-webui-openpose-editor`
117
+ - `ComfyUI-WanVideoWrapper`: DWPoseモデルの利用、プロポーション変換、ポーズレンダリングの参考
118
+ - URL: `https://github.com/kijai/ComfyUI-WanVideoWrapper`
issues/001_プロジェクト基本構造構築.md ADDED
@@ -0,0 +1,67 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Issue: プロジェクト基本構造構築
2
+
3
+ ## 概要
4
+ dwpose-editorプロジェクトの基本的なディレクトリ構造とエントリーポイントを作成する。
5
+
6
+ ## 背景
7
+ 新規プロジェクトとして、dwpose-editorの開発を開始する。`./refs/dwpose_modifier`を参考にしつつ、よりシンプルで理解しやすい構造を目指す。
8
+
9
+ ## 実装内容
10
+
11
+ ### 1. ディレクトリ構造の作成
12
+ ```
13
+ dwpose-editor/
14
+ ├── app.py # メインアプリケーション
15
+ ├── requirements.txt # 依存関係
16
+ ├── README.md # プロジェクト説明
17
+ ├── utils/ # ユーティリティ関数
18
+ │ ├── __init__.py
19
+ │ ├── pose_utils.py # ポーズ処理関連
20
+ │ └── image_utils.py # 画像処理関連
21
+ ├── static/ # 静的ファイル(JavaScriptなど)
22
+ │ └── pose_editor.js # Canvas操作用JavaScript
23
+ └── templates/ # テンプレートポーズデータ
24
+ └── poses.json # 2頭身・3頭身ポーズテンプレート
25
+ ```
26
+
27
+ ### 2. app.py の基本構造
28
+ ```python
29
+ import gradio as gr
30
+
31
+ def main():
32
+ with gr.Blocks(title="DWPose Editor") as demo:
33
+ gr.Markdown("# DWPose Editor")
34
+ gr.Markdown("2頭身・3頭身キャラクターのポーズ編集ツール")
35
+
36
+ # TODO: UIコンポーネントを追加
37
+
38
+ return demo
39
+
40
+ if __name__ == "__main__":
41
+ demo = main()
42
+ demo.launch()
43
+ ```
44
+
45
+ ### 3. requirements.txt の作成
46
+ ```
47
+ gradio>=4.0.0
48
+ numpy
49
+ pillow
50
+ opencv-python
51
+ ```
52
+
53
+ ### 4. 基本的なREADME.md
54
+ - プロジェクトの説明
55
+ - インストール方法
56
+ - 使用方法
57
+ - Hugging Face Spacesへのデプロイ手順
58
+
59
+ ## 参考
60
+ - `./refs/dwpose_modifier/issues/001_プロジェクト基本構造構築_complete.md`
61
+ - 特に、プロジェクト構造の考え方とGradio Blocksの基本設定を参考にする
62
+
63
+ ## 完了条件
64
+ - [ ] ディレクトリ構造が作成されている
65
+ - [ ] app.pyが実行でき、基本的なGradio UIが表示される
66
+ - [ ] requirements.txtに必要な依存関係が記載されている
67
+ - [ ] README.mdに基本的な情報が記載されている
issues/002_Gradio基本UIレイアウト.md ADDED
@@ -0,0 +1,129 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Issue: Gradio基本UIレイアウト
2
+
3
+ ## 概要
4
+ 仕様書に従って、Gradioを使用した基本的なUIレイアウトを構築する。
5
+
6
+ ## 背景
7
+ Issue #001で作成した基本構造の上に、dwpose-editorの主要なUIコンポーネントを配置する。
8
+
9
+ ## 実装内容
10
+
11
+ ### 1. 全体レイアウト構造
12
+ ```python
13
+ with gr.Blocks() as demo:
14
+ gr.Markdown("# DWPose Editor")
15
+
16
+ with gr.Row():
17
+ # 左側:入力部
18
+ with gr.Column(scale=1):
19
+ # 参考画像アップロード
20
+ # テンプレートポーズ選択
21
+ # キャンバス設定
22
+ # 表示設定
23
+
24
+ # 中央:エディット部
25
+ with gr.Column(scale=2):
26
+ # ポーズ描画キャンバス
27
+
28
+ # 右側:出力部
29
+ with gr.Column(scale=1):
30
+ # ポーズ画像出力
31
+ # JSONデータ出力
32
+ ```
33
+
34
+ ### 2. 入力部コンポーネント
35
+ ```python
36
+ # 参考画像アップロード
37
+ input_image = gr.Image(
38
+ label="参考画像",
39
+ type="pil",
40
+ elem_id="input_image"
41
+ )
42
+
43
+ # テンプレートポーズ選択
44
+ template_dropdown = gr.Dropdown(
45
+ label="テンプレートポーズ",
46
+ choices=["2頭身ポーズ画像 2頭身", "3頭身ポーズ画像 3頭身"],
47
+ value="2頭身ポーズ画像 2頭身"
48
+ )
49
+
50
+ # キャンバスサイズ設定(1行に配置)
51
+ with gr.Row():
52
+ canvas_width = gr.Number(
53
+ label="幅",
54
+ value=512,
55
+ minimum=64,
56
+ maximum=2048,
57
+ step=64
58
+ )
59
+ canvas_height = gr.Number(
60
+ label="高さ",
61
+ value=512,
62
+ minimum=64,
63
+ maximum=2048,
64
+ step=64
65
+ )
66
+ update_canvas_btn = gr.Button("Update", variant="primary")
67
+
68
+ # 表示設定グループ
69
+ with gr.Group():
70
+ gr.Markdown("### 表示設定")
71
+ draw_hand = gr.Checkbox(label="手を描画", value=True)
72
+ draw_face = gr.Checkbox(label="顔を描画", value=True)
73
+
74
+ edit_mode = gr.Radio(
75
+ label="編集モード",
76
+ choices=["簡易モード", "詳細モード"],
77
+ value="簡易モード"
78
+ )
79
+ ```
80
+
81
+ ### 3. エディット部(Canvas)
82
+ ```python
83
+ # ポーズ描画キャンバス
84
+ pose_canvas = gr.HTML(
85
+ elem_id="pose_canvas_container",
86
+ value='<canvas id="pose_canvas" width="640" height="640" style="border: 1px solid #ccc;"></canvas>'
87
+ )
88
+
89
+ # 非表示のデータ保持用コンポーネント
90
+ pose_data = gr.JSON(visible=False, value={})
91
+ ```
92
+
93
+ ### 4. 出力部コンポーネント
94
+ ```python
95
+ # ポーズ画像出力
96
+ output_image = gr.Image(
97
+ label="ポーズ画像",
98
+ type="pil",
99
+ elem_id="output_image"
100
+ )
101
+
102
+ # ポーズ画像ダウンロードボタン
103
+ download_image_btn = gr.Button("画像をダウンロード", variant="secondary")
104
+
105
+ # JSONデータ表示
106
+ output_json = gr.JSON(
107
+ label="ポーズデータ (JSON)",
108
+ elem_id="output_json"
109
+ )
110
+
111
+ # JSONダウンロードボタン
112
+ download_json_btn = gr.Button("JSONをダウンロード", variant="secondary")
113
+ ```
114
+
115
+ ### 5. スタイリング考慮事項
116
+ - キャンバスは固定表示サイズ640x640
117
+ - レスポンシブ対応は必須ではない(仕様書に記載なし)
118
+ - Hugging Face Spacesでの表示を考慮
119
+
120
+ ## 参考
121
+ - `./refs/dwpose_modifier`のUIレイアウト
122
+ - 特にタブ1とタブ2のマージされた形を意識
123
+
124
+ ## 完了条件
125
+ - [ ] 3カラムレイアウトが正しく表示される
126
+ - [ ] すべての入力コンポーネントが配置されている
127
+ - [ ] Canvasエリアが正しく表示される(640x640)
128
+ - [ ] 出力部のコンポーネントが配置されている
129
+ - [ ] elem_idが適切に設定されている(JavaScript連携用)
issues/003_DWPoseモデル統合.md ADDED
@@ -0,0 +1,141 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Issue: DWPoseモデル統合
2
+
3
+ ## 概要
4
+ Hugging FaceからDWPoseモデルを取得し、画像からポーズを検出する機能を実装する。
5
+
6
+ ## 背景
7
+ DWPoseは、OpenPoseと異なり、つま先、手、顔の詳細情報を含む高精度なポーズ推定モデル。`./refs/dwpose_modifier`の実装を参考にしつつ、シンプルな構造で統合する。
8
+
9
+ ## 実装内容
10
+
11
+ ### 1. 必要な依存関係の追加
12
+ requirements.txtに以下を追加:
13
+ ```
14
+ huggingface-hub
15
+ onnxruntime
16
+ torch # CUDA検出用
17
+ ```
18
+
19
+ ### 2. DWPoseマネージャーの作成
20
+ `utils/dwpose_manager.py`:
21
+ ```python
22
+ import os
23
+ from huggingface_hub import hf_hub_download
24
+ import onnxruntime as ort
25
+
26
+ class DWPoseManager:
27
+ def __init__(self):
28
+ self.model_repo = "yzd-v/DWPose"
29
+ self.cache_dir = "./models"
30
+ self.yolox_session = None
31
+ self.dwpose_session = None
32
+
33
+ def initialize(self):
34
+ """モデルのダウンロードと初期化"""
35
+ try:
36
+ # YOLOXモデル
37
+ yolox_path = hf_hub_download(
38
+ repo_id=self.model_repo,
39
+ filename="yolox_l.onnx",
40
+ cache_dir=self.cache_dir
41
+ )
42
+
43
+ # DWPoseモデル
44
+ dwpose_path = hf_hub_download(
45
+ repo_id=self.model_repo,
46
+ filename="dw-ll_ucoco_384.onnx",
47
+ cache_dir=self.cache_dir
48
+ )
49
+
50
+ # ONNXセッション作成
51
+ providers = ['CUDAExecutionProvider', 'CPUExecutionProvider']
52
+ self.yolox_session = ort.InferenceSession(yolox_path, providers=providers)
53
+ self.dwpose_session = ort.InferenceSession(dwpose_path, providers=providers)
54
+
55
+ return True
56
+ except Exception as e:
57
+ return False, str(e)
58
+ ```
59
+
60
+ ### 3. DWPose検出器の実装
61
+ `utils/dwpose_detector.py`:
62
+ ```python
63
+ import numpy as np
64
+ import cv2
65
+
66
+ class DWPoseDetector:
67
+ def __init__(self, manager):
68
+ self.manager = manager
69
+ self.input_size = 640 # YOLOX入力サイズ
70
+ self.pose_input_size = 384 # DWPose入力サイズ
71
+
72
+ def detect(self, image):
73
+ """画像からポーズを検出"""
74
+ try:
75
+ # 1. 人物検出(YOLOX)
76
+ persons = self._detect_persons(image)
77
+ if len(persons) == 0:
78
+ return None, "人物が検出されませんでした"
79
+
80
+ # 2. ポーズ推定(DWPose)
81
+ poses = []
82
+ for person_box in persons:
83
+ pose = self._detect_pose(image, person_box)
84
+ if pose is not None:
85
+ poses.append(pose)
86
+
87
+ # 3. 最も大きい人物のポーズを返す
88
+ if len(poses) > 0:
89
+ return poses[0], None
90
+ else:
91
+ return None, "ポーズを検出できませんでした"
92
+
93
+ except Exception as e:
94
+ return None, f"エラーが発生しました: {str(e)}"
95
+ ```
96
+
97
+ ### 4. ポーズデータ形式
98
+ DWPoseの出力形式(フラット配列):
99
+ ```python
100
+ # 各キーポイント: [x, y, confidence] × キーポイント数
101
+ # - body: 17キーポイント
102
+ # - foot: 6キーポイント(つま先含む)
103
+ # - face: 68キーポイント
104
+ # - hand: 左右各21キーポイント
105
+ pose_data = {
106
+ "bodies": {
107
+ "candidate": [[x1, y1], [x2, y2], ...], # 全キーポイントの座標
108
+ "subset": [[indices..., score, count]] # 人物ごとのキーポイントインデックス
109
+ },
110
+ "faces": [...], # 顔のキーポイント
111
+ "hands": [...], # 手のキーポイント
112
+ "resolution": [width, height]
113
+ }
114
+ ```
115
+
116
+ ### 5. エラーハンドリング
117
+ ```python
118
+ def safe_detect(detector, image):
119
+ """安全なポーズ検出(Gradio用)"""
120
+ try:
121
+ pose_data, error = detector.detect(image)
122
+ if error:
123
+ gr.Info(error) # Gradioトースト通知
124
+ return None
125
+ return pose_data
126
+ except Exception as e:
127
+ gr.Info(f"予期しないエラー: {str(e)}")
128
+ return None
129
+ ```
130
+
131
+ ## 参考
132
+ - `./refs/dwpose_modifier/core/dwpose/`の実装
133
+ - 特に`manager.py`、`detection/detector.py`を重点的に参考
134
+ - OpenPose形式への変換ロジック
135
+
136
+ ## 完了条件
137
+ - [ ] Hugging Faceからモデルが自動ダウンロードされる
138
+ - [ ] 画像を入力してポーズが検出できる
139
+ - [ ] エラー時にGradioトーストで通知される
140
+ - [ ] つま先、手、顔の情報が含まれている
141
+ - [ ] dwpose_modifierと同じデータ形式で出力される
issues/004_Canvas要素初期化.md ADDED
@@ -0,0 +1,177 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Issue: Canvas要素初期化
2
+
3
+ ## 概要
4
+ GradioとCanvasの連携を確立し、JavaScriptによるCanvas操作の基盤を構築する。dwpose_modifierで苦労した初期化問題を回避する。
5
+
6
+ ## 背景
7
+ Canvas初期化はdwpose_modifierで最も苦労した部分。Gradio要素の遅延生成に対応した堅牢な初期化処理が必要。
8
+
9
+ ## 実装内容
10
+
11
+ ### 1. JavaScript埋め込み構造
12
+ `static/pose_editor.js`:
13
+ ```javascript
14
+ // グローバル変数
15
+ let canvas = null;
16
+ let ctx = null;
17
+ let poseData = null;
18
+ let isInitialized = false;
19
+
20
+ // デバッグログ関数
21
+ function debugLog(message) {
22
+ console.log(`[DWPose Editor] ${new Date().toISOString()} - ${message}`);
23
+ }
24
+
25
+ // Canvas初期化関数(複数の初期化タイミングに対応)
26
+ function initializeCanvas() {
27
+ debugLog("initializeCanvas called");
28
+
29
+ if (isInitialized) {
30
+ debugLog("Already initialized, skipping");
31
+ return;
32
+ }
33
+
34
+ canvas = document.getElementById('pose_canvas');
35
+ if (!canvas) {
36
+ debugLog("Canvas not found, retrying...");
37
+ setTimeout(initializeCanvas, 100);
38
+ return;
39
+ }
40
+
41
+ ctx = canvas.getContext('2d');
42
+ if (!ctx) {
43
+ debugLog("Failed to get 2d context");
44
+ return;
45
+ }
46
+
47
+ // Canvas設定
48
+ canvas.width = 640;
49
+ canvas.height = 640;
50
+
51
+ // 初期描画
52
+ clearCanvas();
53
+
54
+ isInitialized = true;
55
+ debugLog("Canvas initialized successfully");
56
+ }
57
+
58
+ // 複数の初期化トリガー
59
+ document.addEventListener('DOMContentLoaded', initializeCanvas);
60
+ window.addEventListener('load', initializeCanvas);
61
+
62
+ // Gradio固有の初期化(MutationObserver使用)
63
+ const observer = new MutationObserver((mutations) => {
64
+ if (document.getElementById('pose_canvas') && !isInitialized) {
65
+ initializeCanvas();
66
+ }
67
+ });
68
+
69
+ // body要素の監視開始
70
+ document.addEventListener('DOMContentLoaded', () => {
71
+ observer.observe(document.body, {
72
+ childList: true,
73
+ subtree: true
74
+ });
75
+ });
76
+ ```
77
+
78
+ ### 2. Gradioとの連携
79
+ `app.py`での埋め込み:
80
+ ```python
81
+ def load_javascript():
82
+ """JavaScriptファイルを読み込む"""
83
+ js_path = os.path.join(os.path.dirname(__file__), "static", "pose_editor.js")
84
+ with open(js_path, "r", encoding="utf-8") as f:
85
+ return f"<script>{f.read()}</script>"
86
+
87
+ # Gradio Blocksでの使用
88
+ with gr.Blocks(head=load_javascript()) as demo:
89
+ # UI定義
90
+ pose_canvas = gr.HTML(
91
+ elem_id="pose_canvas_container",
92
+ value='<canvas id="pose_canvas" width="640" height="640" style="border: 1px solid #ccc; cursor: crosshair;"></canvas>'
93
+ )
94
+ ```
95
+
96
+ ### 3. Canvas基本操作関数
97
+ ```javascript
98
+ // Canvas クリア
99
+ function clearCanvas() {
100
+ if (!ctx) return;
101
+ ctx.fillStyle = '#f0f0f0';
102
+ ctx.fillRect(0, 0, canvas.width, canvas.height);
103
+ }
104
+
105
+ // エラー表示
106
+ function showCanvasError(message) {
107
+ if (!ctx) return;
108
+ clearCanvas();
109
+ ctx.fillStyle = '#ff0000';
110
+ ctx.font = '16px Arial';
111
+ ctx.textAlign = 'center';
112
+ ctx.fillText(message, canvas.width / 2, canvas.height / 2);
113
+ }
114
+
115
+ // Canvas状態チェック
116
+ function isCanvasReady() {
117
+ return canvas && ctx && isInitialized;
118
+ }
119
+ ```
120
+
121
+ ### 4. Gradioイベントとの連携準備
122
+ ```javascript
123
+ // Gradioからのデータ受信用
124
+ window.updatePoseData = function(data) {
125
+ debugLog("updatePoseData called");
126
+ if (!isCanvasReady()) {
127
+ debugLog("Canvas not ready");
128
+ return;
129
+ }
130
+
131
+ poseData = data;
132
+ // TODO: 描画処理を呼び出す
133
+ };
134
+
135
+ // Gradioへのデータ送信用
136
+ window.getPoseData = function() {
137
+ return poseData;
138
+ };
139
+ ```
140
+
141
+ ### 5. エラーハンドリング
142
+ ```javascript
143
+ // グローバルエラーハンドラー
144
+ window.addEventListener('error', (e) => {
145
+ debugLog(`Global error: ${e.message}`);
146
+ if (isCanvasReady()) {
147
+ showCanvasError('エラーが発生しました');
148
+ }
149
+ });
150
+
151
+ // Canvas操作のtry-catch
152
+ function safeCanvasOperation(operation) {
153
+ try {
154
+ if (!isCanvasReady()) {
155
+ debugLog("Canvas not ready for operation");
156
+ return false;
157
+ }
158
+ operation();
159
+ return true;
160
+ } catch (e) {
161
+ debugLog(`Canvas operation error: ${e.message}`);
162
+ return false;
163
+ }
164
+ }
165
+ ```
166
+
167
+ ## 参考
168
+ - `./refs/dwpose_modifier/issues/019_test_gradio_simple実験継続_complete.md`
169
+ - `./refs/dwpose_modifier/issues/023_キャンバス描画重複問題修正_complete.md`
170
+ - 特に初期化タイミングとMutationObserverの使用法
171
+
172
+ ## 完了条件
173
+ - [ ] ページロード時にCanvasが正しく初期化される
174
+ - [ ] Gradio要素の遅延生成に対応している
175
+ - [ ] デバッグログが適切に出力される
176
+ - [ ] エラー時に適切なメッセージが表示される
177
+ - [ ] Canvas操作の基本関数が実装されている
issues/005_ポーズ基本描画機能.md ADDED
@@ -0,0 +1,208 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Issue: ポーズ基本描画機能
2
+
3
+ ## 概要
4
+ DWPoseデータをCanvas上に描画する基本機能を実装する。dwpose_modifierの描画ロジックを参考に、スティックフィギュア形式で表示。
5
+
6
+ ## 背景
7
+ Issue #004で初期化したCanvas上に、DWPoseのキーポイントと接続線を描画する。色分けもdwpose_modifierに準拠。
8
+
9
+ ## 実装内容
10
+
11
+ ### 1. DWPose接続定義
12
+ `static/pose_editor.js`に追加:
13
+ ```javascript
14
+ // DWPoseの接続定義(dwpose_modifierと同じ)
15
+ const BODY_CONNECTIONS = [
16
+ [0, 1], [1, 2], [2, 3], [3, 4], // 右腕
17
+ [0, 5], [5, 6], [6, 7], [7, 8], // 左腕
18
+ [0, 9], [9, 10], [10, 11], // 右脚
19
+ [0, 12], [12, 13], [13, 14], // 左脚
20
+ [0, 15], [15, 16] // 首・頭
21
+ ];
22
+
23
+ // 色定義(dwpose_modifierから)
24
+ const POSE_COLORS = {
25
+ body: '#ff0055',
26
+ hand: '#ff9500',
27
+ face: '#00ff00',
28
+ bodyLine: '#ff0055',
29
+ handLine: '#ff9500',
30
+ faceLine: '#00ff00'
31
+ };
32
+
33
+ // キーポイント半径
34
+ const KEYPOINT_RADIUS = 4;
35
+ ```
36
+
37
+ ### 2. 基本描画関数
38
+ ```javascript
39
+ // ポーズ全体の描画
40
+ function drawPose(poseData, drawHands = true, drawFace = true) {
41
+ if (!isCanvasReady() || !poseData) return;
42
+
43
+ clearCanvas();
44
+
45
+ // 背景画像があれば描画(後で実装)
46
+
47
+ // ボディの描画
48
+ drawBody(poseData);
49
+
50
+ // 手の描画
51
+ if (drawHands && poseData.hands) {
52
+ drawHands(poseData.hands);
53
+ }
54
+
55
+ // 顔の描画
56
+ if (drawFace && poseData.faces) {
57
+ drawFace(poseData.faces);
58
+ }
59
+ }
60
+
61
+ // ボディ描画
62
+ function drawBody(poseData) {
63
+ if (!poseData.bodies || !poseData.bodies.candidate) return;
64
+
65
+ const candidates = poseData.bodies.candidate;
66
+ const subset = poseData.bodies.subset;
67
+
68
+ if (subset.length === 0) return;
69
+
70
+ // 最初の人物のみ描画(単一人物想定)
71
+ const person = subset[0];
72
+
73
+ // 接続線の描画
74
+ ctx.strokeStyle = POSE_COLORS.bodyLine;
75
+ ctx.lineWidth = 3;
76
+
77
+ for (const [start, end] of BODY_CONNECTIONS) {
78
+ const startIdx = person[start];
79
+ const endIdx = person[end];
80
+
81
+ if (startIdx >= 0 && endIdx >= 0) {
82
+ const startPoint = candidates[startIdx];
83
+ const endPoint = candidates[endIdx];
84
+
85
+ if (startPoint && endPoint) {
86
+ ctx.beginPath();
87
+ ctx.moveTo(startPoint[0], startPoint[1]);
88
+ ctx.lineTo(endPoint[0], endPoint[1]);
89
+ ctx.stroke();
90
+ }
91
+ }
92
+ }
93
+
94
+ // キーポイントの描画
95
+ ctx.fillStyle = POSE_COLORS.body;
96
+ for (let i = 0; i < 17; i++) { // DWPoseボディは17キーポイント
97
+ const idx = person[i];
98
+ if (idx >= 0) {
99
+ const point = candidates[idx];
100
+ if (point) {
101
+ drawKeypoint(point[0], point[1]);
102
+ }
103
+ }
104
+ }
105
+ }
106
+
107
+ // キーポイント描画
108
+ function drawKeypoint(x, y, radius = KEYPOINT_RADIUS) {
109
+ ctx.beginPath();
110
+ ctx.arc(x, y, radius, 0, Math.PI * 2);
111
+ ctx.fill();
112
+ }
113
+ ```
114
+
115
+ ### 3. 手と顔の描画(簡易版)
116
+ ```javascript
117
+ // 手の描画(21キーポイント × 2)
118
+ function drawHands(handsData) {
119
+ if (!handsData || handsData.length === 0) return;
120
+
121
+ ctx.fillStyle = POSE_COLORS.hand;
122
+ ctx.strokeStyle = POSE_COLORS.handLine;
123
+ ctx.lineWidth = 2;
124
+
125
+ // 左右の手を描画
126
+ handsData.forEach((hand) => {
127
+ if (hand && hand.length > 0) {
128
+ // TODO: 手の接続線定義を追加
129
+ // とりあえずキーポイントのみ描画
130
+ for (let i = 0; i < hand.length; i += 3) {
131
+ const x = hand[i];
132
+ const y = hand[i + 1];
133
+ const conf = hand[i + 2];
134
+
135
+ if (conf > 0.3) { // 信頼度閾値
136
+ drawKeypoint(x, y, 3);
137
+ }
138
+ }
139
+ }
140
+ });
141
+ }
142
+
143
+ // 顔の描画(68キーポイント)
144
+ function drawFace(facesData) {
145
+ if (!facesData || facesData.length === 0) return;
146
+
147
+ ctx.fillStyle = POSE_COLORS.face;
148
+
149
+ const face = facesData[0]; // 最初の顔のみ
150
+ if (face && face.length > 0) {
151
+ for (let i = 0; i < face.length; i += 3) {
152
+ const x = face[i];
153
+ const y = face[i + 1];
154
+ const conf = face[i + 2];
155
+
156
+ if (conf > 0.3) {
157
+ drawKeypoint(x, y, 2);
158
+ }
159
+ }
160
+ }
161
+ }
162
+ ```
163
+
164
+ ### 4. 座標変換対応
165
+ ```javascript
166
+ // データ解像度とCanvas表示サイズの変換
167
+ function transformCoordinate(x, y, dataWidth, dataHeight) {
168
+ const scaleX = canvas.width / dataWidth;
169
+ const scaleY = canvas.height / dataHeight;
170
+
171
+ return {
172
+ x: x * scaleX,
173
+ y: y * scaleY
174
+ };
175
+ }
176
+
177
+ // 描画時に座標変換を適用
178
+ function drawKeypointScaled(x, y, dataRes, radius = KEYPOINT_RADIUS) {
179
+ const scaled = transformCoordinate(x, y, dataRes[0], dataRes[1]);
180
+ drawKeypoint(scaled.x, scaled.y, radius);
181
+ }
182
+ ```
183
+
184
+ ### 5. Gradioからの呼び出し
185
+ ```javascript
186
+ // Gradioからポーズデータを受け取って描画
187
+ window.updatePoseData = function(data, drawHands, drawFace) {
188
+ debugLog("updatePoseData called");
189
+ if (!isCanvasReady()) {
190
+ debugLog("Canvas not ready");
191
+ return;
192
+ }
193
+
194
+ poseData = data;
195
+ drawPose(poseData, drawHands, drawFace);
196
+ };
197
+ ```
198
+
199
+ ## 参考
200
+ - `./refs/dwpose_modifier`の描画ロジック
201
+ - 特に色定義、接続定義、座標変換処理
202
+
203
+ ## 完了条件
204
+ - [ ] DWPoseのボディが正しく描画される
205
+ - [ ] 手と顔のキーポイントが描画される
206
+ - [ ] 色分けがdwpose_modifierと同じ
207
+ - [ ] 座標変換が正しく機能する
208
+ - [ ] チェックボックスに応じて手顔の表示/非表示が切り替わる
issues/006_画像アップロード処理.md ADDED
@@ -0,0 +1,219 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Issue: 画像アップロード処理
2
+
3
+ ## 概要
4
+ 参考画像のアップロード機能を実装。画像はCanvasの背景として表示し、長辺を表示サイズに合わせてリサイズする。
5
+
6
+ ## 背景
7
+ ユーザーがアップロードした画像を背景として表示し、その上にポーズを編集できるようにする。
8
+
9
+ ## 実装内容
10
+
11
+ ### 1. 画像処理ユーティリティ
12
+ `utils/image_utils.py`:
13
+ ```python
14
+ from PIL import Image
15
+ import numpy as np
16
+
17
+ def resize_image_aspect_ratio(image, target_size=640):
18
+ """
19
+ 画像の長辺をtarget_sizeに合わせてアスペクト比を保持したままリサイズ
20
+ """
21
+ width, height = image.size
22
+
23
+ # 長辺を特定
24
+ if width > height:
25
+ # 横長
26
+ new_width = target_size
27
+ new_height = int(height * (target_size / width))
28
+ else:
29
+ # 縦長または正方形
30
+ new_height = target_size
31
+ new_width = int(width * (target_size / height))
32
+
33
+ # リサイズ
34
+ resized = image.resize((new_width, new_height), Image.Resampling.LANCZOS)
35
+
36
+ # Canvasサイズに合わせて中央配置用の情報を返す
37
+ offset_x = (target_size - new_width) // 2
38
+ offset_y = (target_size - new_height) // 2
39
+
40
+ return resized, offset_x, offset_y
41
+
42
+ def image_to_base64(image):
43
+ """PIL ImageをBase64エンコード"""
44
+ import io
45
+ import base64
46
+
47
+ buffer = io.BytesIO()
48
+ image.save(buffer, format="PNG")
49
+ img_str = base64.b64encode(buffer.getvalue()).decode()
50
+ return f"data:image/png;base64,{img_str}"
51
+ ```
52
+
53
+ ### 2. Gradio画像アップロードハンドラ
54
+ `app.py`に追加:
55
+ ```python
56
+ def handle_image_upload(image):
57
+ """
58
+ 画像アップロード時の処理
59
+ """
60
+ if image is None:
61
+ return None, None, gr.update()
62
+
63
+ try:
64
+ # 画像をリサイズ
65
+ resized_img, offset_x, offset_y = resize_image_aspect_ratio(image)
66
+
67
+ # Base64エンコード(JavaScript用)
68
+ img_base64 = image_to_base64(resized_img)
69
+
70
+ # 画像情報を保存
71
+ image_info = {
72
+ "base64": img_base64,
73
+ "width": resized_img.width,
74
+ "height": resized_img.height,
75
+ "offset_x": offset_x,
76
+ "offset_y": offset_y,
77
+ "original_size": image.size
78
+ }
79
+
80
+ # JavaScriptに画像を送信
81
+ update_js = f"""
82
+ <script>
83
+ setTimeout(() => {{
84
+ if (window.updateBackgroundImage) {{
85
+ window.updateBackgroundImage({json.dumps(image_info)});
86
+ }}
87
+ }}, 100);
88
+ </script>
89
+ """
90
+
91
+ return image_info, update_js, gr.Info("画像をアップロードしました")
92
+
93
+ except Exception as e:
94
+ return None, None, gr.Error(f"画像処理エラー: {str(e)}")
95
+
96
+ # UIコンポーネントとの接続
97
+ input_image.upload(
98
+ handle_image_upload,
99
+ inputs=[input_image],
100
+ outputs=[image_data, js_executor, None]
101
+ )
102
+ ```
103
+
104
+ ### 3. JavaScript側の背景画像表示
105
+ `static/pose_editor.js`に追加:
106
+ ```javascript
107
+ // 背景画像管理
108
+ let backgroundImage = null;
109
+ let backgroundImageInfo = null;
110
+
111
+ // 背景画像の更新
112
+ window.updateBackgroundImage = function(imageInfo) {
113
+ debugLog("updateBackgroundImage called");
114
+
115
+ if (!imageInfo || !imageInfo.base64) {
116
+ backgroundImage = null;
117
+ backgroundImageInfo = null;
118
+ redrawCanvas();
119
+ return;
120
+ }
121
+
122
+ // 新しい画像オブジェクトを作成
123
+ const img = new Image();
124
+ img.onload = function() {
125
+ backgroundImage = img;
126
+ backgroundImageInfo = imageInfo;
127
+ debugLog(`Background image loaded: ${imageInfo.width}x${imageInfo.height}`);
128
+ redrawCanvas();
129
+ };
130
+
131
+ img.onerror = function() {
132
+ debugLog("Failed to load background image");
133
+ backgroundImage = null;
134
+ backgroundImageInfo = null;
135
+ };
136
+
137
+ img.src = imageInfo.base64;
138
+ };
139
+
140
+ // 背景画像の描画
141
+ function drawBackgroundImage() {
142
+ if (!backgroundImage || !backgroundImageInfo) return;
143
+
144
+ ctx.save();
145
+
146
+ // 画像を中央に配置
147
+ ctx.drawImage(
148
+ backgroundImage,
149
+ backgroundImageInfo.offset_x,
150
+ backgroundImageInfo.offset_y,
151
+ backgroundImageInfo.width,
152
+ backgroundImageInfo.height
153
+ );
154
+
155
+ ctx.restore();
156
+ }
157
+
158
+ // Canvas再描画(背景込み)
159
+ function redrawCanvas() {
160
+ if (!isCanvasReady()) return;
161
+
162
+ clearCanvas();
163
+ drawBackgroundImage();
164
+
165
+ // ポーズがあれば描画
166
+ if (poseData) {
167
+ drawPose(poseData, currentDrawHands, currentDrawFace);
168
+ }
169
+ }
170
+ ```
171
+
172
+ ### 4. 画像座標とポーズ座標の整合性
173
+ ```javascript
174
+ // 画像上の座標をCanvas座標に変換
175
+ function imageToCanvasCoord(x, y) {
176
+ if (!backgroundImageInfo) return {x, y};
177
+
178
+ // 画像のオフセットを考慮
179
+ return {
180
+ x: x + backgroundImageInfo.offset_x,
181
+ y: y + backgroundImageInfo.offset_y
182
+ };
183
+ }
184
+
185
+ // Canvas座標を画像座標に変換
186
+ function canvasToImageCoord(x, y) {
187
+ if (!backgroundImageInfo) return {x, y};
188
+
189
+ return {
190
+ x: x - backgroundImageInfo.offset_x,
191
+ y: y - backgroundImageInfo.offset_y
192
+ };
193
+ }
194
+ ```
195
+
196
+ ### 5. 画像なしでのDWPose失敗時の処理
197
+ ```javascript
198
+ // DWPose検出失敗時も画像は表示
199
+ window.handleDWPoseError = function(imageInfo) {
200
+ debugLog("DWPose detection failed, showing image only");
201
+
202
+ // 画像だけ表示
203
+ updateBackgroundImage(imageInfo);
204
+
205
+ // ポーズデータをクリア
206
+ poseData = null;
207
+ };
208
+ ```
209
+
210
+ ## 参考
211
+ - 画像リサイズとアスペクト比保持のロジック
212
+ - Base64エンコーディングによるJavaScript連携
213
+
214
+ ## 完了条件
215
+ - [ ] 画像アップロード時に長辺が640pxにリサイズされる
216
+ - [ ] アスペクト比が保持される
217
+ - [ ] 画像がCanvasの中央に表示される
218
+ - [ ] DWPose失敗時も画像が表示される
219
+ - [ ] 画像座標とCanvas座標の変換が正しく機能する
issues/007_DWPose自動抽出機能.md ADDED
@@ -0,0 +1,209 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Issue: DWPose自動抽出機能
2
+
3
+ ## 概要
4
+ 画像アップロード時に自動的にDWPoseでポーズを抽出し、Canvas上に表示する機能を実装。
5
+
6
+ ## 背景
7
+ Issue #003で統合したDWPoseモデルと、Issue #006の画像アップロード処理を連携させる。
8
+
9
+ ## 実装内容
10
+
11
+ ### 1. DWPose抽出処理の実装
12
+ `app.py`に追加:
13
+ ```python
14
+ # グローバル変数としてDWPoseマネージャーを保持
15
+ dwpose_manager = None
16
+ dwpose_detector = None
17
+
18
+ def initialize_dwpose():
19
+ """DWPoseの初期化"""
20
+ global dwpose_manager, dwpose_detector
21
+
22
+ try:
23
+ dwpose_manager = DWPoseManager()
24
+ success = dwpose_manager.initialize()
25
+
26
+ if success:
27
+ dwpose_detector = DWPoseDetector(dwpose_manager)
28
+ return True, "DWPoseモデルを初期化しました"
29
+ else:
30
+ return False, "DWPoseモデルの初期化に失敗しました"
31
+ except Exception as e:
32
+ return False, f"初期化エラー: {str(e)}"
33
+
34
+ def extract_pose_from_image(image, image_info):
35
+ """
36
+ 画像からポーズを抽出
37
+ """
38
+ if image is None or dwpose_detector is None:
39
+ return None, "画像またはDWPoseが利用できません"
40
+
41
+ try:
42
+ # numpy配列に変換
43
+ image_np = np.array(image)
44
+
45
+ # DWPoseでポーズ検出
46
+ pose_data, error = dwpose_detector.detect(image_np)
47
+
48
+ if error:
49
+ return None, error
50
+
51
+ if pose_data is None:
52
+ return None, "ポーズが検出されませんでした"
53
+
54
+ # 解像度情報を追加
55
+ pose_data["resolution"] = [image.width, image.height]
56
+
57
+ return pose_data, None
58
+
59
+ except Exception as e:
60
+ return None, f"ポーズ抽出エラー: {str(e)}"
61
+ ```
62
+
63
+ ### 2. 画像アップロードとDWPose連携
64
+ ```python
65
+ def handle_image_upload_with_dwpose(image):
66
+ """
67
+ 画像アップロード時の処理(DWPose自動実行)
68
+ """
69
+ if image is None:
70
+ return None, None, None, gr.update()
71
+
72
+ try:
73
+ # 画像をリサイズ
74
+ resized_img, offset_x, offset_y = resize_image_aspect_ratio(image)
75
+
76
+ # Base64エンコード
77
+ img_base64 = image_to_base64(resized_img)
78
+
79
+ image_info = {
80
+ "base64": img_base64,
81
+ "width": resized_img.width,
82
+ "height": resized_img.height,
83
+ "offset_x": offset_x,
84
+ "offset_y": offset_y,
85
+ "original_size": image.size
86
+ }
87
+
88
+ # DWPoseでポーズ抽出
89
+ gr.Info("ポーズを検出中...")
90
+ pose_data, error = extract_pose_from_image(image, image_info)
91
+
92
+ if error:
93
+ # エラーでも画像は表示
94
+ update_js = f"""
95
+ <script>
96
+ setTimeout(() => {{
97
+ if (window.handleDWPoseError) {{
98
+ window.handleDWPoseError({json.dumps(image_info)});
99
+ }}
100
+ }}, 100);
101
+ </script>
102
+ """
103
+ gr.Warning(error)
104
+ return image_info, None, update_js, gr.update()
105
+
106
+ # 成功時:画像とポーズ両方を更新
107
+ update_js = f"""
108
+ <script>
109
+ setTimeout(() => {{
110
+ if (window.updateBackgroundImage) {{
111
+ window.updateBackgroundImage({json.dumps(image_info)});
112
+ }}
113
+ if (window.updatePoseData) {{
114
+ window.updatePoseData({json.dumps(pose_data)}, true, true);
115
+ }}
116
+ }}, 100);
117
+ </script>
118
+ """
119
+
120
+ gr.Info("ポーズを検出しました")
121
+ return image_info, pose_data, update_js, gr.update(value=pose_data)
122
+
123
+ except Exception as e:
124
+ return None, None, None, gr.Error(f"処理エラー: {str(e)}")
125
+ ```
126
+
127
+ ### 3. ポーズ座標の正規化チェック
128
+ `utils/pose_utils.py`:
129
+ ```python
130
+ def normalize_pose_coordinates(pose_data, image_width, image_height):
131
+ """
132
+ ポーズ座標が正規化されているかチェックし、必要なら変換
133
+ """
134
+ if not pose_data or "bodies" not in pose_data:
135
+ return pose_data
136
+
137
+ candidates = pose_data["bodies"].get("candidate", [])
138
+ if not candidates:
139
+ return pose_data
140
+
141
+ # 最初のキーポイントで判定
142
+ first_point = candidates[0]
143
+ if len(first_point) >= 2:
144
+ x, y = first_point[0], first_point[1]
145
+
146
+ # 0-1の範囲なら正規化されている
147
+ if 0 <= x <= 1 and 0 <= y <= 1:
148
+ # ピクセル座標に変換
149
+ for i, point in enumerate(candidates):
150
+ if len(point) >= 2:
151
+ candidates[i][0] = point[0] * image_width
152
+ candidates[i][1] = point[1] * image_height
153
+
154
+ return pose_data
155
+ ```
156
+
157
+ ### 4. エラーリカバリー機能
158
+ ```python
159
+ def retry_dwpose_detection(image):
160
+ """
161
+ DWPose検出のリトライ(パラメータ調整)
162
+ """
163
+ # 検出閾値を下げてリトライ
164
+ original_threshold = dwpose_detector.detection_threshold
165
+ dwpose_detector.detection_threshold = 0.3 # より低い閾値
166
+
167
+ try:
168
+ pose_data, error = extract_pose_from_image(image, None)
169
+ return pose_data, error
170
+ finally:
171
+ # 閾値を元に戻す
172
+ dwpose_detector.detection_threshold = original_threshold
173
+ ```
174
+
175
+ ### 5. デバッグ情報の出力
176
+ ```python
177
+ def debug_pose_data(pose_data):
178
+ """ポーズデータのデバッグ情報を出力"""
179
+ if not pose_data:
180
+ print("No pose data")
181
+ return
182
+
183
+ if "bodies" in pose_data:
184
+ bodies = pose_data["bodies"]
185
+ print(f"Candidates: {len(bodies.get('candidate', []))}")
186
+ print(f"Subsets: {len(bodies.get('subset', []))}")
187
+
188
+ if bodies.get('subset'):
189
+ person = bodies['subset'][0]
190
+ detected_keypoints = sum(1 for idx in person[:17] if idx >= 0)
191
+ print(f"Detected keypoints: {detected_keypoints}/17")
192
+
193
+ if "faces" in pose_data:
194
+ print(f"Face keypoints: {len(pose_data['faces'][0]) // 3 if pose_data['faces'] else 0}")
195
+
196
+ if "hands" in pose_data:
197
+ print(f"Hands detected: {len(pose_data['hands'])}")
198
+ ```
199
+
200
+ ## 参考
201
+ - `./refs/dwpose_modifier`のDWPose検出フロー
202
+ - エラーハンドリングとリトライロジック
203
+
204
+ ## 完了条件
205
+ - [ ] 画像アップロード時に自動でDWPoseが実行される
206
+ - [ ] ポーズが検出されたらCanvas上に表示される
207
+ - [ ] エラー時はGradioトーストで通知される
208
+ - [ ] エラー時も画像は表示される
209
+ - [ ] 座標の正規化チェックが機能する
issues/008_座標変換システム.md ADDED
@@ -0,0 +1,285 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Issue: 座標変換システム
2
+
3
+ ## 概要
4
+ データ解像度とCanvas表示サイズの違いを吸収する座標変換システムを実装。dwpose_modifierで苦労した座標系の問題を解決。
5
+
6
+ ## 背景
7
+ - データ解像度:512x512(可変)
8
+ - Canvas表示サイズ:640x640(固定)
9
+ - 画像サイズ:任意(アスペクト比保持)
10
+
11
+ これらの座標系を統一的に扱うシステムが必要。
12
+
13
+ ## 実装内容
14
+
15
+ ### 1. 座標変換クラス
16
+ `utils/coordinate_system.py`:
17
+ ```python
18
+ class CoordinateTransformer:
19
+ def __init__(self):
20
+ self.data_width = 512
21
+ self.data_height = 512
22
+ self.canvas_width = 640
23
+ self.canvas_height = 640
24
+ self.image_width = None
25
+ self.image_height = None
26
+ self.image_offset_x = 0
27
+ self.image_offset_y = 0
28
+
29
+ def set_data_resolution(self, width, height):
30
+ """データ解像度の設定"""
31
+ self.data_width = width
32
+ self.data_height = height
33
+
34
+ def set_image_info(self, width, height, offset_x, offset_y):
35
+ """画像情報の設定"""
36
+ self.image_width = width
37
+ self.image_height = height
38
+ self.image_offset_x = offset_x
39
+ self.image_offset_y = offset_y
40
+
41
+ def data_to_canvas(self, x, y):
42
+ """データ座標をCanvas座標に変換"""
43
+ scale_x = self.canvas_width / self.data_width
44
+ scale_y = self.canvas_height / self.data_height
45
+
46
+ return {
47
+ 'x': x * scale_x,
48
+ 'y': y * scale_y
49
+ }
50
+
51
+ def canvas_to_data(self, x, y):
52
+ """Canvas座標をデータ座標に変換"""
53
+ scale_x = self.data_width / self.canvas_width
54
+ scale_y = self.data_height / self.canvas_height
55
+
56
+ return {
57
+ 'x': x * scale_x,
58
+ 'y': y * scale_y
59
+ }
60
+
61
+ def image_to_canvas(self, x, y):
62
+ """画像座標をCanvas座標に変換"""
63
+ return {
64
+ 'x': x + self.image_offset_x,
65
+ 'y': y + self.image_offset_y
66
+ }
67
+
68
+ def canvas_to_image(self, x, y):
69
+ """Canvas座標を画像座標に変換"""
70
+ return {
71
+ 'x': x - self.image_offset_x,
72
+ 'y': y - self.image_offset_y
73
+ }
74
+ ```
75
+
76
+ ### 2. JavaScript座標変換
77
+ `static/pose_editor.js`に追加:
78
+ ```javascript
79
+ // 座標変換管理
80
+ class CoordinateManager {
81
+ constructor() {
82
+ this.dataWidth = 512;
83
+ this.dataHeight = 512;
84
+ this.canvasWidth = 640;
85
+ this.canvasHeight = 640;
86
+ this.imageInfo = null;
87
+ }
88
+
89
+ setDataResolution(width, height) {
90
+ this.dataWidth = width;
91
+ this.dataHeight = height;
92
+ debugLog(`Data resolution set to ${width}x${height}`);
93
+ }
94
+
95
+ setImageInfo(imageInfo) {
96
+ this.imageInfo = imageInfo;
97
+ debugLog(`Image info set: ${imageInfo.width}x${imageInfo.height} offset(${imageInfo.offset_x}, ${imageInfo.offset_y})`);
98
+ }
99
+
100
+ // データ座標 → Canvas座標
101
+ dataToCanvas(x, y) {
102
+ const scaleX = this.canvasWidth / this.dataWidth;
103
+ const scaleY = this.canvasHeight / this.dataHeight;
104
+
105
+ return {
106
+ x: x * scaleX,
107
+ y: y * scaleY
108
+ };
109
+ }
110
+
111
+ // Canvas座標 → データ座標
112
+ canvasToData(x, y) {
113
+ const scaleX = this.dataWidth / this.canvasWidth;
114
+ const scaleY = this.dataHeight / this.canvasHeight;
115
+
116
+ return {
117
+ x: x * scaleX,
118
+ y: y * scaleY
119
+ };
120
+ }
121
+
122
+ // 画像座標 → Canvas座標
123
+ imageToCanvas(x, y) {
124
+ if (!this.imageInfo) return {x, y};
125
+
126
+ return {
127
+ x: x + this.imageInfo.offset_x,
128
+ y: y + this.imageInfo.offset_y
129
+ };
130
+ }
131
+
132
+ // Canvas座標 → 画像座標
133
+ canvasToImage(x, y) {
134
+ if (!this.imageInfo) return {x, y};
135
+
136
+ return {
137
+ x: x - this.imageInfo.offset_x,
138
+ y: y - this.imageInfo.offset_y
139
+ };
140
+ }
141
+
142
+ // 正規化座標検出と変換
143
+ normalizeIfNeeded(pose_data) {
144
+ if (!pose_data || !pose_data.bodies) return pose_data;
145
+
146
+ const candidates = pose_data.bodies.candidate;
147
+ if (!candidates || candidates.length === 0) return pose_data;
148
+
149
+ // 最初のキーポイントで正規化チェック
150
+ const firstPoint = candidates[0];
151
+ if (firstPoint && firstPoint.length >= 2) {
152
+ const x = firstPoint[0];
153
+ const y = firstPoint[1];
154
+
155
+ // 0-1の範囲なら正規化されている
156
+ if (x >= 0 && x <= 1 && y >= 0 && y <= 1) {
157
+ debugLog("Normalizing coordinates to pixel values");
158
+
159
+ // ピクセル座標に変換
160
+ for (let i = 0; i < candidates.length; i++) {
161
+ if (candidates[i] && candidates[i].length >= 2) {
162
+ candidates[i][0] *= this.dataWidth;
163
+ candidates[i][1] *= this.dataHeight;
164
+ }
165
+ }
166
+
167
+ // 手と顔も同様に処理
168
+ if (pose_data.hands) {
169
+ pose_data.hands.forEach(hand => {
170
+ if (hand) {
171
+ for (let i = 0; i < hand.length; i += 3) {
172
+ hand[i] *= this.dataWidth; // x
173
+ hand[i + 1] *= this.dataHeight; // y
174
+ }
175
+ }
176
+ });
177
+ }
178
+
179
+ if (pose_data.faces) {
180
+ pose_data.faces.forEach(face => {
181
+ if (face) {
182
+ for (let i = 0; i < face.length; i += 3) {
183
+ face[i] *= this.dataWidth; // x
184
+ face[i + 1] *= this.dataHeight; // y
185
+ }
186
+ }
187
+ });
188
+ }
189
+ }
190
+ }
191
+
192
+ return pose_data;
193
+ }
194
+ }
195
+
196
+ // グローバル座標マネージャー
197
+ const coordManager = new CoordinateManager();
198
+ ```
199
+
200
+ ### 3. ポーズ描画での座標変換適用
201
+ ```javascript
202
+ // 座標変換を適用した描画関数
203
+ function drawKeypointTransformed(x, y, radius = KEYPOINT_RADIUS) {
204
+ const canvasCoord = coordManager.dataToCanvas(x, y);
205
+ drawKeypoint(canvasCoord.x, canvasCoord.y, radius);
206
+ }
207
+
208
+ function drawLineTransformed(x1, y1, x2, y2) {
209
+ const start = coordManager.dataToCanvas(x1, y1);
210
+ const end = coordManager.dataToCanvas(x2, y2);
211
+
212
+ ctx.beginPath();
213
+ ctx.moveTo(start.x, start.y);
214
+ ctx.lineTo(end.x, end.y);
215
+ ctx.stroke();
216
+ }
217
+
218
+ // ボディ描画を座標変換対応に更新
219
+ function drawBodyTransformed(poseData) {
220
+ if (!poseData.bodies || !poseData.bodies.candidate) return;
221
+
222
+ const candidates = poseData.bodies.candidate;
223
+ const subset = poseData.bodies.subset;
224
+
225
+ if (subset.length === 0) return;
226
+
227
+ const person = subset[0];
228
+
229
+ // 接続線の描画(座標変換適用)
230
+ ctx.strokeStyle = POSE_COLORS.bodyLine;
231
+ ctx.lineWidth = 3;
232
+
233
+ for (const [start, end] of BODY_CONNECTIONS) {
234
+ const startIdx = person[start];
235
+ const endIdx = person[end];
236
+
237
+ if (startIdx >= 0 && endIdx >= 0) {
238
+ const startPoint = candidates[startIdx];
239
+ const endPoint = candidates[endIdx];
240
+
241
+ if (startPoint && endPoint) {
242
+ drawLineTransformed(
243
+ startPoint[0], startPoint[1],
244
+ endPoint[0], endPoint[1]
245
+ );
246
+ }
247
+ }
248
+ }
249
+
250
+ // キーポイントの描画(座標変換適用)
251
+ ctx.fillStyle = POSE_COLORS.body;
252
+ for (let i = 0; i < 17; i++) {
253
+ const idx = person[i];
254
+ if (idx >= 0) {
255
+ const point = candidates[idx];
256
+ if (point) {
257
+ drawKeypointTransformed(point[0], point[1]);
258
+ }
259
+ }
260
+ }
261
+ }
262
+ ```
263
+
264
+ ### 4. マウス操作での座標変換
265
+ ```javascript
266
+ // マウス座標をデータ座標に変換
267
+ function getDataCoordinateFromMouse(event) {
268
+ const rect = canvas.getBoundingClientRect();
269
+ const canvasX = event.clientX - rect.left;
270
+ const canvasY = event.clientY - rect.top;
271
+
272
+ return coordManager.canvasToData(canvasX, canvasY);
273
+ }
274
+ ```
275
+
276
+ ## 参考
277
+ - `./refs/dwpose_modifier`の座標変換ロジック
278
+ - 特に正規化座標の検出と変換処理
279
+
280
+ ## 完了条件
281
+ - [ ] データ解像度とCanvas表示サイズの変換が正しく機能する
282
+ - [ ] 正規化座標の自動検出と変換ができる
283
+ - [ ] マウス操作時の座標変換が正確
284
+ - [ ] 画像オフセットを考慮した座標変換
285
+ - [ ] 手と顔の座標変換も対応している
issues/009_キーポイントドラッグ基本.md ADDED
@@ -0,0 +1,333 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Issue: キーポイントドラッグ基本
2
+
3
+ ## 概要
4
+ Canvas上のキーポイントをマウスでドラッグして編集する基本機能を実装。詳細モードでの個別キーポイント操作。
5
+
6
+ ## 背景
7
+ Issue #008の座標変換システムを使って、キーポイントのドラッグ編集を実現する。dwpose_modifierで発生したドラッグ時の座標ジャンプ問題を回避。
8
+
9
+ ## 実装内容
10
+
11
+ ### 1. ドラッグ状態管理
12
+ `static/pose_editor.js`に追加:
13
+ ```javascript
14
+ // ドラッグ状態管理
15
+ class DragManager {
16
+ constructor() {
17
+ this.isDragging = false;
18
+ this.dragTarget = null;
19
+ this.dragOffset = {x: 0, y: 0};
20
+ this.clickThreshold = 10; // ドラッグ判定閾値(ピクセル)
21
+ this.hoverTarget = null;
22
+ }
23
+
24
+ startDrag(target, mouseX, mouseY) {
25
+ this.isDragging = true;
26
+ this.dragTarget = target;
27
+
28
+ // ドラッグオフセットを計算(キーポイント中心からのずれ)
29
+ const canvasCoord = coordManager.dataToCanvas(target.x, target.y);
30
+ this.dragOffset = {
31
+ x: mouseX - canvasCoord.x,
32
+ y: mouseY - canvasCoord.y
33
+ };
34
+
35
+ debugLog(`Drag started: ${target.type} ${target.index}`);
36
+ }
37
+
38
+ updateDrag(mouseX, mouseY) {
39
+ if (!this.isDragging || !this.dragTarget) return false;
40
+
41
+ // マウス位置からオフセットを引いて正確な位置を計算
42
+ const adjustedX = mouseX - this.dragOffset.x;
43
+ const adjustedY = mouseY - this.dragOffset.y;
44
+
45
+ // Canvas座標をデータ座標に変換
46
+ const dataCoord = coordManager.canvasToData(adjustedX, adjustedY);
47
+
48
+ // キーポイント位置を更新
49
+ this.updateKeypointPosition(this.dragTarget, dataCoord.x, dataCoord.y);
50
+
51
+ return true;
52
+ }
53
+
54
+ endDrag() {
55
+ if (this.isDragging) {
56
+ debugLog(`Drag ended: ${this.dragTarget?.type} ${this.dragTarget?.index}`);
57
+ }
58
+
59
+ this.isDragging = false;
60
+ this.dragTarget = null;
61
+ this.dragOffset = {x: 0, y: 0};
62
+ }
63
+
64
+ updateKeypointPosition(target, x, y) {
65
+ if (!poseData) return;
66
+
67
+ if (target.type === 'body') {
68
+ const candidates = poseData.bodies.candidate;
69
+ const subset = poseData.bodies.subset[0];
70
+ const candidateIndex = subset[target.index];
71
+
72
+ if (candidateIndex >= 0 && candidates[candidateIndex]) {
73
+ candidates[candidateIndex][0] = x;
74
+ candidates[candidateIndex][1] = y;
75
+ }
76
+ } else if (target.type === 'hand') {
77
+ const hand = poseData.hands[target.handIndex];
78
+ if (hand) {
79
+ const pointIndex = target.index * 3;
80
+ hand[pointIndex] = x; // x座標
81
+ hand[pointIndex + 1] = y; // y座標
82
+ }
83
+ } else if (target.type === 'face') {
84
+ const face = poseData.faces[0];
85
+ if (face) {
86
+ const pointIndex = target.index * 3;
87
+ face[pointIndex] = x; // x座標
88
+ face[pointIndex + 1] = y; // y座標
89
+ }
90
+ }
91
+ }
92
+ }
93
+
94
+ const dragManager = new DragManager();
95
+ ```
96
+
97
+ ### 2. キーポイント検出
98
+ ```javascript
99
+ // マウス位置にあるキーポイントを検出
100
+ function getKeypointAt(mouseX, mouseY) {
101
+ if (!poseData) return null;
102
+
103
+ const hitRadius = 12; // ヒット判定半径
104
+
105
+ // ボディキーポイントをチェック
106
+ if (poseData.bodies && poseData.bodies.candidate) {
107
+ const candidates = poseData.bodies.candidate;
108
+ const subset = poseData.bodies.subset[0];
109
+
110
+ for (let i = 0; i < 17; i++) {
111
+ const candidateIndex = subset[i];
112
+ if (candidateIndex >= 0) {
113
+ const point = candidates[candidateIndex];
114
+ if (point) {
115
+ const canvasCoord = coordManager.dataToCanvas(point[0], point[1]);
116
+ const distance = Math.sqrt(
117
+ Math.pow(mouseX - canvasCoord.x, 2) +
118
+ Math.pow(mouseY - canvasCoord.y, 2)
119
+ );
120
+
121
+ if (distance <= hitRadius) {
122
+ return {
123
+ type: 'body',
124
+ index: i,
125
+ x: point[0],
126
+ y: point[1]
127
+ };
128
+ }
129
+ }
130
+ }
131
+ }
132
+ }
133
+
134
+ // 手のキーポイントをチェック(詳細モード時)
135
+ if (currentEditMode === 'detailed' && currentDrawHands && poseData.hands) {
136
+ for (let handIndex = 0; handIndex < poseData.hands.length; handIndex++) {
137
+ const hand = poseData.hands[handIndex];
138
+ if (hand) {
139
+ for (let i = 0; i < hand.length; i += 3) {
140
+ const x = hand[i];
141
+ const y = hand[i + 1];
142
+ const conf = hand[i + 2];
143
+
144
+ if (conf > 0.3) {
145
+ const canvasCoord = coordManager.dataToCanvas(x, y);
146
+ const distance = Math.sqrt(
147
+ Math.pow(mouseX - canvasCoord.x, 2) +
148
+ Math.pow(mouseY - canvasCoord.y, 2)
149
+ );
150
+
151
+ if (distance <= hitRadius) {
152
+ return {
153
+ type: 'hand',
154
+ handIndex: handIndex,
155
+ index: i / 3,
156
+ x: x,
157
+ y: y
158
+ };
159
+ }
160
+ }
161
+ }
162
+ }
163
+ }
164
+ }
165
+
166
+ // 顔のキーポイントをチェック(詳細モード時)
167
+ if (currentEditMode === 'detailed' && currentDrawFace && poseData.faces) {
168
+ const face = poseData.faces[0];
169
+ if (face) {
170
+ for (let i = 0; i < face.length; i += 3) {
171
+ const x = face[i];
172
+ const y = face[i + 1];
173
+ const conf = face[i + 2];
174
+
175
+ if (conf > 0.3) {
176
+ const canvasCoord = coordManager.dataToCanvas(x, y);
177
+ const distance = Math.sqrt(
178
+ Math.pow(mouseX - canvasCoord.x, 2) +
179
+ Math.pow(mouseY - canvasCoord.y, 2)
180
+ );
181
+
182
+ if (distance <= hitRadius) {
183
+ return {
184
+ type: 'face',
185
+ index: i / 3,
186
+ x: x,
187
+ y: y
188
+ };
189
+ }
190
+ }
191
+ }
192
+ }
193
+ }
194
+
195
+ return null;
196
+ }
197
+ ```
198
+
199
+ ### 3. マウスイベントハンドラ
200
+ ```javascript
201
+ // マウスイベントの設定
202
+ function setupMouseEvents() {
203
+ if (!canvas) return;
204
+
205
+ canvas.addEventListener('mousedown', handleMouseDown);
206
+ canvas.addEventListener('mousemove', handleMouseMove);
207
+ canvas.addEventListener('mouseup', handleMouseUp);
208
+ canvas.addEventListener('mouseleave', handleMouseLeave);
209
+ }
210
+
211
+ function handleMouseDown(event) {
212
+ if (!isCanvasReady()) return;
213
+
214
+ const rect = canvas.getBoundingClientRect();
215
+ const mouseX = event.clientX - rect.left;
216
+ const mouseY = event.clientY - rect.top;
217
+
218
+ const target = getKeypointAt(mouseX, mouseY);
219
+ if (target && currentEditMode === 'detailed') {
220
+ dragManager.startDrag(target, mouseX, mouseY);
221
+ canvas.style.cursor = 'grabbing';
222
+ event.preventDefault();
223
+ }
224
+ }
225
+
226
+ function handleMouseMove(event) {
227
+ if (!isCanvasReady()) return;
228
+
229
+ const rect = canvas.getBoundingClientRect();
230
+ const mouseX = event.clientX - rect.left;
231
+ const mouseY = event.clientY - rect.top;
232
+
233
+ if (dragManager.isDragging) {
234
+ // ドラッグ中
235
+ if (dragManager.updateDrag(mouseX, mouseY)) {
236
+ redrawCanvas();
237
+ }
238
+ } else {
239
+ // ホバー処理
240
+ const target = getKeypointAt(mouseX, mouseY);
241
+ if (target && currentEditMode === 'detailed') {
242
+ canvas.style.cursor = 'grab';
243
+ dragManager.hoverTarget = target;
244
+ } else {
245
+ canvas.style.cursor = 'default';
246
+ dragManager.hoverTarget = null;
247
+ }
248
+ }
249
+ }
250
+
251
+ function handleMouseUp(event) {
252
+ if (dragManager.isDragging) {
253
+ dragManager.endDrag();
254
+ canvas.style.cursor = 'default';
255
+
256
+ // Gradioにポーズデータを送信
257
+ if (window.notifyPoseDataChanged) {
258
+ window.notifyPoseDataChanged(poseData);
259
+ }
260
+ }
261
+ }
262
+
263
+ function handleMouseLeave(event) {
264
+ dragManager.endDrag();
265
+ canvas.style.cursor = 'default';
266
+ }
267
+ ```
268
+
269
+ ### 4. ホバー時の視覚フィードバック
270
+ ```javascript
271
+ // ホバー時のキーポイント強調表示
272
+ function drawKeypointHighlight(x, y, radius = KEYPOINT_RADIUS) {
273
+ const canvasCoord = coordManager.dataToCanvas(x, y);
274
+
275
+ // 外側の輪
276
+ ctx.save();
277
+ ctx.strokeStyle = '#ffffff';
278
+ ctx.lineWidth = 3;
279
+ ctx.beginPath();
280
+ ctx.arc(canvasCoord.x, canvasCoord.y, radius + 4, 0, Math.PI * 2);
281
+ ctx.stroke();
282
+
283
+ // 内側の輪
284
+ ctx.strokeStyle = '#000000';
285
+ ctx.lineWidth = 1;
286
+ ctx.beginPath();
287
+ ctx.arc(canvasCoord.x, canvasCoord.y, radius + 4, 0, Math.PI * 2);
288
+ ctx.stroke();
289
+ ctx.restore();
290
+ }
291
+
292
+ // 描画関数にホバー表示を追加
293
+ function redrawCanvasWithHover() {
294
+ redrawCanvas();
295
+
296
+ // ホバーターゲットがあれば強調表示
297
+ if (dragManager.hoverTarget && !dragManager.isDragging) {
298
+ drawKeypointHighlight(
299
+ dragManager.hoverTarget.x,
300
+ dragManager.hoverTarget.y
301
+ );
302
+ }
303
+ }
304
+ ```
305
+
306
+ ### 5. デバウンス処理
307
+ ```javascript
308
+ // ドラッグ更新のデバウンス
309
+ let dragUpdateTimeout = null;
310
+
311
+ function debouncedDragUpdate() {
312
+ if (dragUpdateTimeout) {
313
+ clearTimeout(dragUpdateTimeout);
314
+ }
315
+
316
+ dragUpdateTimeout = setTimeout(() => {
317
+ if (window.notifyPoseDataChanged) {
318
+ window.notifyPoseDataChanged(poseData);
319
+ }
320
+ }, 100); // 100ms後に更新
321
+ }
322
+ ```
323
+
324
+ ## 参考
325
+ - `./refs/dwpose_modifier/issues/021_ドラッグ時座標ジャンプ修正_complete.md`
326
+ - 特にオフセット計算とドラッグ精度の改善
327
+
328
+ ## 完了条件
329
+ - [ ] キーポイントがマウスでドラッグできる
330
+ - [ ] ドラッグ時に座標ジャンプが発生しない
331
+ - [ ] ホバー時に視覚フィードバックがある
332
+ - [ ] 詳細モードでのみドラッグが有効
333
+ - [ ] ドラッグ後にポーズデータが更新される
issues/010_手描画チェックボックス.md ADDED
@@ -0,0 +1,44 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Issue: 手描画チェックボックス
2
+
3
+ ## 概要
4
+ 手のキーポイントの表示/非表示を切り替えるチェックボックス機能を実装。
5
+
6
+ ## 実装内容
7
+
8
+ ### 1. Gradioイベントハンドラ
9
+ ```python
10
+ def toggle_hand_drawing(show_hands):
11
+ """手描画の切り替え"""
12
+ update_js = f"""
13
+ <script>
14
+ setTimeout(() => {{
15
+ if (window.setHandDrawing) {{
16
+ window.setHandDrawing({json.dumps(show_hands)});
17
+ }}
18
+ }}, 50);
19
+ </script>
20
+ """
21
+ return update_js
22
+
23
+ # UIイベント設定
24
+ draw_hand.change(
25
+ toggle_hand_drawing,
26
+ inputs=[draw_hand],
27
+ outputs=[js_executor]
28
+ )
29
+ ```
30
+
31
+ ### 2. JavaScript実装
32
+ ```javascript
33
+ let currentDrawHands = true;
34
+
35
+ window.setHandDrawing = function(showHands) {
36
+ currentDrawHands = showHands;
37
+ debugLog(`Hand drawing: ${showHands}`);
38
+ redrawCanvas();
39
+ };
40
+ ```
41
+
42
+ ## 完了条件
43
+ - [ ] チェックボックスで手の表示/非表示が切り替わる
44
+ - [ ] リアルタイムで反映される
issues/011_顔描画チェックボックス.md ADDED
@@ -0,0 +1,44 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Issue: 顔描画チェックボックス
2
+
3
+ ## 概要
4
+ 顔のキーポイントの表示/非表示を切り替えるチェックボックス機能を実装。
5
+
6
+ ## 実装内容
7
+
8
+ ### 1. Gradioイベントハンドラ
9
+ ```python
10
+ def toggle_face_drawing(show_face):
11
+ """顔描画の切り替え"""
12
+ update_js = f"""
13
+ <script>
14
+ setTimeout(() => {{
15
+ if (window.setFaceDrawing) {{
16
+ window.setFaceDrawing({json.dumps(show_face)});
17
+ }}
18
+ }}, 50);
19
+ </script>
20
+ """
21
+ return update_js
22
+
23
+ # UIイベント設定
24
+ draw_face.change(
25
+ toggle_face_drawing,
26
+ inputs=[draw_face],
27
+ outputs=[js_executor]
28
+ )
29
+ ```
30
+
31
+ ### 2. JavaScript実装
32
+ ```javascript
33
+ let currentDrawFace = true;
34
+
35
+ window.setFaceDrawing = function(showFace) {
36
+ currentDrawFace = showFace;
37
+ debugLog(`Face drawing: ${showFace}`);
38
+ redrawCanvas();
39
+ };
40
+ ```
41
+
42
+ ## 完了条件
43
+ - [ ] チェックボックスで顔の表示/非表示が切り替わる
44
+ - [ ] 手描画とは独立して動作する
issues/012_簡易モード矩形選択.md ADDED
@@ -0,0 +1,100 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Issue: 簡易モード矩形選択
2
+
3
+ ## 概要
4
+ 簡易モードで手と顔を矩形として表示し、矩形クリックで編集モードに入る機能を実装。
5
+
6
+ ## 背景
7
+ dwpose_modifierのタブ1にある機能。手21キーポイントと顔68キーポイントを矩形で代表し、直感的な編集を可能にする。
8
+
9
+ ## 実装内容
10
+
11
+ ### 1. 矩形計算ユーティリティ
12
+ ```javascript
13
+ function calculateHandRectangle(handData) {
14
+ if (!handData || handData.length === 0) return null;
15
+
16
+ let minX = Infinity, minY = Infinity;
17
+ let maxX = -Infinity, maxY = -Infinity;
18
+
19
+ for (let i = 0; i < handData.length; i += 3) {
20
+ const x = handData[i];
21
+ const y = handData[i + 1];
22
+ const conf = handData[i + 2];
23
+
24
+ if (conf > 0.3) {
25
+ minX = Math.min(minX, x);
26
+ minY = Math.min(minY, y);
27
+ maxX = Math.max(maxX, x);
28
+ maxY = Math.max(maxY, y);
29
+ }
30
+ }
31
+
32
+ if (minX === Infinity) return null;
33
+
34
+ // マージンを追加
35
+ const margin = 10;
36
+ return {
37
+ x: minX - margin,
38
+ y: minY - margin,
39
+ width: maxX - minX + margin * 2,
40
+ height: maxY - minY + margin * 2
41
+ };
42
+ }
43
+
44
+ function calculateFaceRectangle(faceData) {
45
+ // 同様の実装
46
+ }
47
+ ```
48
+
49
+ ### 2. 矩形描画
50
+ ```javascript
51
+ function drawRectangle(rect, color, isSelected = false) {
52
+ const canvasRect = {
53
+ x: coordManager.dataToCanvas(rect.x, 0).x,
54
+ y: coordManager.dataToCanvas(0, rect.y).y,
55
+ width: rect.width * (640 / coordManager.dataWidth),
56
+ height: rect.height * (640 / coordManager.dataHeight)
57
+ };
58
+
59
+ ctx.strokeStyle = color;
60
+ ctx.lineWidth = isSelected ? 3 : 2;
61
+ ctx.setLineDash(isSelected ? [] : [5, 5]);
62
+ ctx.strokeRect(canvasRect.x, canvasRect.y, canvasRect.width, canvasRect.height);
63
+ ctx.setLineDash([]);
64
+ }
65
+ ```
66
+
67
+ ### 3. 矩形クリック検出
68
+ ```javascript
69
+ function getRectangleAt(mouseX, mouseY) {
70
+ if (currentEditMode !== 'simple') return null;
71
+
72
+ // 手の矩形チェック
73
+ if (currentDrawHands && poseData.hands) {
74
+ for (let i = 0; i < poseData.hands.length; i++) {
75
+ const rect = calculateHandRectangle(poseData.hands[i]);
76
+ if (rect && isPointInRectangle(mouseX, mouseY, rect)) {
77
+ return {type: 'hand', index: i, rect: rect};
78
+ }
79
+ }
80
+ }
81
+
82
+ // 顔の矩形チェック
83
+ if (currentDrawFace && poseData.faces) {
84
+ const rect = calculateFaceRectangle(poseData.faces[0]);
85
+ if (rect && isPointInRectangle(mouseX, mouseY, rect)) {
86
+ return {type: 'face', index: 0, rect: rect};
87
+ }
88
+ }
89
+
90
+ return null;
91
+ }
92
+ ```
93
+
94
+ ## 参考
95
+ - `./refs/dwpose_modifier/issues/028_手顔キーポイント矩形操作機能.md`
96
+
97
+ ## 完了条件
98
+ - [ ] 簡易モードで手と顔が矩形表示される
99
+ - [ ] 矩形クリックで選択状態になる
100
+ - [ ] 矩形ドラッグで一括移動できる
issues/013_詳細モード個別編集.md ADDED
@@ -0,0 +1,70 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Issue: 詳細モード個別編集
2
+
3
+ ## 概要
4
+ 詳細モードで手と顔の個別キーポイントを直接編集できる機能。Issue #009のドラッグ機能を手と顔に拡張。
5
+
6
+ ## 実装内容
7
+
8
+ ### 1. 詳細モード描画
9
+ ```javascript
10
+ function drawDetailedHands(handsData) {
11
+ if (!handsData || !currentDrawHands) return;
12
+
13
+ ctx.fillStyle = POSE_COLORS.hand;
14
+
15
+ handsData.forEach((hand, handIndex) => {
16
+ if (hand && hand.length > 0) {
17
+ for (let i = 0; i < hand.length; i += 3) {
18
+ const x = hand[i];
19
+ const y = hand[i + 1];
20
+ const conf = hand[i + 2];
21
+
22
+ if (conf > 0.3) {
23
+ drawKeypointTransformed(x, y, 3);
24
+ }
25
+ }
26
+ }
27
+ });
28
+ }
29
+
30
+ function drawDetailedFace(facesData) {
31
+ if (!facesData || !currentDrawFace) return;
32
+
33
+ ctx.fillStyle = POSE_COLORS.face;
34
+
35
+ const face = facesData[0];
36
+ if (face && face.length > 0) {
37
+ for (let i = 0; i < face.length; i += 3) {
38
+ const x = face[i];
39
+ const y = face[i + 1];
40
+ const conf = face[i + 2];
41
+
42
+ if (conf > 0.3) {
43
+ drawKeypointTransformed(x, y, 2);
44
+ }
45
+ }
46
+ }
47
+ }
48
+ ```
49
+
50
+ ### 2. キーポイント選択表示
51
+ ```javascript
52
+ function drawSelectedKeypoint(target) {
53
+ if (!target) return;
54
+
55
+ const canvasCoord = coordManager.dataToCanvas(target.x, target.y);
56
+
57
+ ctx.save();
58
+ ctx.strokeStyle = '#00ff00';
59
+ ctx.lineWidth = 2;
60
+ ctx.beginPath();
61
+ ctx.arc(canvasCoord.x, canvasCoord.y, 8, 0, Math.PI * 2);
62
+ ctx.stroke();
63
+ ctx.restore();
64
+ }
65
+ ```
66
+
67
+ ## 完了条件
68
+ - [ ] 詳細モードで手顔の個別キーポイントが表示される
69
+ - [ ] 各キーポイントをドラッグで編集できる
70
+ - [ ] 選択時に視覚フィードバックがある
issues/014_モード切替ラジオボタン.md ADDED
@@ -0,0 +1,53 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Issue: モード切替ラジオボタン
2
+
3
+ ## 概要
4
+ 簡易モードと詳細モードを切り替えるラジオボタン機能を実装。
5
+
6
+ ## 実装内容
7
+
8
+ ### 1. Gradioイベントハンドラ
9
+ ```python
10
+ def change_edit_mode(mode):
11
+ """編集モードの切り替え"""
12
+ update_js = f"""
13
+ <script>
14
+ setTimeout(() => {{
15
+ if (window.setEditMode) {{
16
+ window.setEditMode('{mode}');
17
+ }}
18
+ }}, 50);
19
+ </script>
20
+ """
21
+ return update_js
22
+
23
+ edit_mode.change(
24
+ change_edit_mode,
25
+ inputs=[edit_mode],
26
+ outputs=[js_executor]
27
+ )
28
+ ```
29
+
30
+ ### 2. JavaScript実装
31
+ ```javascript
32
+ let currentEditMode = 'simple';
33
+
34
+ window.setEditMode = function(mode) {
35
+ currentEditMode = mode;
36
+ debugLog(`Edit mode changed to: ${mode}`);
37
+
38
+ // ドラッグ状態をリセット
39
+ dragManager.endDrag();
40
+
41
+ // カーソルをリセット
42
+ if (canvas) {
43
+ canvas.style.cursor = 'default';
44
+ }
45
+
46
+ redrawCanvas();
47
+ };
48
+ ```
49
+
50
+ ## 完了条件
51
+ - [ ] ラジオボタンでモード切替ができる
52
+ - [ ] モード切替時に表示が更新される
53
+ - [ ] ドラッグ状態が適切にリセットされる
issues/015_Canvas解像度変更機能.md ADDED
@@ -0,0 +1,82 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Issue: Canvas解像度変更機能
2
+
3
+ ## 概要
4
+ Canvasのデータ解像度を変更する機能。表示サイズは640x640固定、データ解像度のみ変更。
5
+
6
+ ## 実装内容
7
+
8
+ ### 1. Gradioイベントハンドラ
9
+ ```python
10
+ def update_canvas_resolution(width, height):
11
+ """Canvas解像度の更新"""
12
+ try:
13
+ width = int(width)
14
+ height = int(height)
15
+
16
+ if width < 64 or width > 2048 or height < 64 or height > 2048:
17
+ return gr.Warning("解像度は64-2048の範囲で設定してください")
18
+
19
+ update_js = f"""
20
+ <script>
21
+ setTimeout(() => {{
22
+ if (window.updateCanvasResolution) {{
23
+ window.updateCanvasResolution({width}, {height});
24
+ }}
25
+ }}, 50);
26
+ </script>
27
+ """
28
+
29
+ return update_js, gr.Info(f"解像度を{width}x{height}に変更しました")
30
+
31
+ except ValueError:
32
+ return None, gr.Error("無効な解像度値です")
33
+
34
+ update_canvas_btn.click(
35
+ update_canvas_resolution,
36
+ inputs=[canvas_width, canvas_height],
37
+ outputs=[js_executor, None]
38
+ )
39
+ ```
40
+
41
+ ### 2. JavaScript実装
42
+ ```javascript
43
+ window.updateCanvasResolution = function(width, height) {
44
+ debugLog(`Updating canvas resolution to ${width}x${height}`);
45
+
46
+ // 座標マネージャーの解像度を更新
47
+ coordManager.setDataResolution(width, height);
48
+
49
+ // 既存のポーズデータがあれば再スケール
50
+ if (poseData) {
51
+ rescalePoseData(poseData, width, height);
52
+ }
53
+
54
+ redrawCanvas();
55
+ };
56
+
57
+ function rescalePoseData(pose_data, newWidth, newHeight) {
58
+ const oldWidth = coordManager.dataWidth;
59
+ const oldHeight = coordManager.dataHeight;
60
+
61
+ const scaleX = newWidth / oldWidth;
62
+ const scaleY = newHeight / oldHeight;
63
+
64
+ // ボディキーポイントのスケール
65
+ if (pose_data.bodies && pose_data.bodies.candidate) {
66
+ pose_data.bodies.candidate.forEach(point => {
67
+ if (point && point.length >= 2) {
68
+ point[0] *= scaleX;
69
+ point[1] *= scaleY;
70
+ }
71
+ });
72
+ }
73
+
74
+ // 手と顔も同様にスケール
75
+ // ...
76
+ }
77
+ ```
78
+
79
+ ## 完了条件
80
+ - [ ] 解像度変更ボタンが機能する
81
+ - [ ] 既存ポーズデータが新解像度にスケールされる
82
+ - [ ] 範囲外の値でエラー表示される
issues/016_テンプレートポーズ選択.md ADDED
@@ -0,0 +1,92 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Issue: テンプレートポーズ選択
2
+
3
+ ## 概要
4
+ 2頭身・3頭身のテンプレートポーズを選択してCanvasに読み込む機能を実装。
5
+
6
+ ## 実装内容
7
+
8
+ ### 1. テンプレートデータ準備
9
+ `templates/poses.json`:
10
+ ```json
11
+ {
12
+ "2頭身ポーズ画像 2頭身": {
13
+ "bodies": {
14
+ "candidate": [[100, 50], [120, 80], ...],
15
+ "subset": [[0, 1, 2, ...]]
16
+ },
17
+ "hands": [],
18
+ "faces": [],
19
+ "resolution": [512, 512]
20
+ },
21
+ "3頭身ポーズ画像 3頭身": {
22
+ "bodies": {
23
+ "candidate": [[100, 40], [115, 70], ...],
24
+ "subset": [[0, 1, 2, ...]]
25
+ },
26
+ "hands": [],
27
+ "faces": [],
28
+ "resolution": [512, 512]
29
+ }
30
+ }
31
+ ```
32
+
33
+ ### 2. Gradioイベントハンドラ
34
+ ```python
35
+ def load_template_pose(template_name):
36
+ """テンプレートポーズの読み込み"""
37
+ try:
38
+ with open("templates/poses.json", "r", encoding="utf-8") as f:
39
+ templates = json.load(f)
40
+
41
+ if template_name not in templates:
42
+ return None, gr.Warning("テンプレートが見つかりません")
43
+
44
+ pose_data = templates[template_name]
45
+
46
+ update_js = f"""
47
+ <script>
48
+ setTimeout(() => {{
49
+ if (window.loadTemplatepose) {{
50
+ window.loadTemplatePose({json.dumps(pose_data)});
51
+ }}
52
+ }}, 50);
53
+ </script>
54
+ """
55
+
56
+ return update_js, gr.Info(f"{template_name}を読み込みました")
57
+
58
+ except Exception as e:
59
+ return None, gr.Error(f"テンプレート読み込みエラー: {str(e)}")
60
+
61
+ template_dropdown.change(
62
+ load_template_pose,
63
+ inputs=[template_dropdown],
64
+ outputs=[js_executor, None]
65
+ )
66
+ ```
67
+
68
+ ### 3. JavaScript実装
69
+ ```javascript
70
+ window.loadTemplatePose = function(templateData) {
71
+ debugLog("Loading template pose");
72
+
73
+ // 背景画像をクリア
74
+ backgroundImage = null;
75
+ backgroundImageInfo = null;
76
+
77
+ // ポーズデータを設定
78
+ poseData = templateData;
79
+
80
+ // 解像度を更新
81
+ if (templateData.resolution) {
82
+ coordManager.setDataResolution(templateData.resolution[0], templateData.resolution[1]);
83
+ }
84
+
85
+ redrawCanvas();
86
+ };
87
+ ```
88
+
89
+ ## 完了条件
90
+ - [ ] ドロップダウンでテンプレート選択できる
91
+ - [ ] 選択したテンプレートがCanvasに表示される
92
+ - [ ] 背景画像がクリアされる
issues/017_ポーズ画像エクスポート.md ADDED
@@ -0,0 +1,71 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Issue: ポーズ画像エクスポート
2
+
3
+ ## 概要
4
+ 編集したポーズをPNG画像としてエクスポートする機能を実装。
5
+
6
+ ## 実装内容
7
+
8
+ ### 1. Pythonでの画像生成
9
+ ```python
10
+ def generate_pose_image(pose_data, width=512, height=512):
11
+ """ポーズデータから画像を生成"""
12
+ try:
13
+ from PIL import Image, ImageDraw
14
+ import numpy as np
15
+
16
+ # 白背景の画像作成
17
+ img = Image.new('RGB', (width, height), 'white')
18
+ draw = ImageDraw.Draw(img)
19
+
20
+ # ポーズ描画ロジック
21
+ if pose_data and 'bodies' in pose_data:
22
+ # ボディ描画
23
+ draw_body_on_image(draw, pose_data['bodies'], width, height)
24
+
25
+ # 手描画
26
+ if 'hands' in pose_data:
27
+ draw_hands_on_image(draw, pose_data['hands'], width, height)
28
+
29
+ # 顔描画
30
+ if 'faces' in pose_data:
31
+ draw_face_on_image(draw, pose_data['faces'], width, height)
32
+
33
+ return img
34
+
35
+ except Exception as e:
36
+ return None
37
+
38
+ def export_pose_image(pose_data):
39
+ """ポーズ画像のエクスポート"""
40
+ if not pose_data:
41
+ return None, gr.Warning("エクスポートするポーズがありません")
42
+
43
+ try:
44
+ img = generate_pose_image(pose_data)
45
+ if img:
46
+ return img, gr.Info("ポーズ画像を生成しました")
47
+ else:
48
+ return None, gr.Error("画像生成に失敗しました")
49
+ except Exception as e:
50
+ return None, gr.Error(f"エクスポートエラー: {str(e)}")
51
+ ```
52
+
53
+ ### 2. JavaScript連携
54
+ ```javascript
55
+ window.requestPoseExport = function() {
56
+ if (!poseData) {
57
+ debugLog("No pose data to export");
58
+ return;
59
+ }
60
+
61
+ // Gradioにポーズデータを送信してエクスポート要求
62
+ if (window.triggerPoseExport) {
63
+ window.triggerPoseExport(poseData);
64
+ }
65
+ };
66
+ ```
67
+
68
+ ## 完了条件
69
+ - [ ] ポーズがPNG画像として生成される
70
+ - [ ] ダウンロードボタンが機能する
71
+ - [ ] 解像度に応じて正しくスケールされる
issues/018_JSONデータエクスポート.md ADDED
@@ -0,0 +1,65 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Issue: JSONデータエクスポート
2
+
3
+ ## 概要
4
+ 編集したポーズデータをJSON形式でエクスポートする機能を実装。
5
+
6
+ ## 実装内容
7
+
8
+ ### 1. Gradioエクスポート機能
9
+ ```python
10
+ def export_pose_json(pose_data):
11
+ """ポーズデータのJSONエクスポート"""
12
+ if not pose_data:
13
+ return None, gr.Warning("エクスポートするポーズがありません")
14
+
15
+ try:
16
+ # JSONファイルとして保存
17
+ import tempfile
18
+ import json
19
+
20
+ with tempfile.NamedTemporaryFile(mode='w', suffix='.json', delete=False) as f:
21
+ json.dump(pose_data, f, indent=2, ensure_ascii=False)
22
+ temp_path = f.name
23
+
24
+ return temp_path, gr.Info("JSONファイルを生成しました")
25
+
26
+ except Exception as e:
27
+ return None, gr.Error(f"JSON生成エラー: {str(e)}")
28
+
29
+ # UIボタンの設定
30
+ download_json_btn.click(
31
+ export_pose_json,
32
+ inputs=[pose_data_state],
33
+ outputs=[json_download_file, None]
34
+ )
35
+ ```
36
+
37
+ ### 2. JavaScript連携
38
+ ```javascript
39
+ window.requestJsonExport = function() {
40
+ if (!poseData) {
41
+ debugLog("No pose data to export");
42
+ return;
43
+ }
44
+
45
+ // Gradioに現在のポーズデータを送信
46
+ if (window.triggerJsonExport) {
47
+ window.triggerJsonExport(poseData);
48
+ }
49
+ };
50
+ ```
51
+
52
+ ### 3. リアルタイム表示
53
+ ```python
54
+ def update_json_display(pose_data):
55
+ """JSON表示の更新"""
56
+ if pose_data:
57
+ return gr.update(value=pose_data)
58
+ else:
59
+ return gr.update(value={})
60
+ ```
61
+
62
+ ## 完了条件
63
+ - [ ] ポーズデータがJSON形式で表示される
64
+ - [ ] JSONファイルとしてダウンロードできる
65
+ - [ ] 編集時にリアルタイムで更新される
issues/019_エラーハンドリング統合.md ADDED
@@ -0,0 +1,94 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Issue: エラーハンドリング統合
2
+
3
+ ## 概要
4
+ アプリケーション全体のエラーハンドリングを統合し、ユーザーフレンドリーなエラー表示を実現。
5
+
6
+ ## 実装内容
7
+
8
+ ### 1. エラー分類と処理
9
+ ```python
10
+ class DWPoseEditorError(Exception):
11
+ """アプリケーション固有のエラー基底クラス"""
12
+ pass
13
+
14
+ class ModelLoadError(DWPoseEditorError):
15
+ """モデル読み込みエラー"""
16
+ pass
17
+
18
+ class PoseDetectionError(DWPoseEditorError):
19
+ """ポーズ検出エラー"""
20
+ pass
21
+
22
+ class ImageProcessingError(DWPoseEditorError):
23
+ """画像処理エラー"""
24
+ pass
25
+
26
+ def handle_error(error, context=""):
27
+ """統一エラーハンドラ"""
28
+ if isinstance(error, ModelLoadError):
29
+ return gr.Error("モデルの読み込みに失敗しました。しばらく待ってから再試行してください。")
30
+ elif isinstance(error, PoseDetectionError):
31
+ return gr.Warning("ポーズを検出できませんでした。別の画像をお試しください。")
32
+ elif isinstance(error, ImageProcessingError):
33
+ return gr.Error("画像の処理中にエラーが発生しました。")
34
+ else:
35
+ return gr.Error(f"予期しないエラーが発生しました: {str(error)}")
36
+ ```
37
+
38
+ ### 2. JavaScript エラーハンドリング
39
+ ```javascript
40
+ // グローバルエラーハンドラ
41
+ window.addEventListener('error', (event) => {
42
+ debugLog(`Global error: ${event.error.message}`);
43
+ if (isCanvasReady()) {
44
+ showCanvasError('エラーが発生しました');
45
+ }
46
+ });
47
+
48
+ // Promise rejection ハンドラ
49
+ window.addEventListener('unhandledrejection', (event) => {
50
+ debugLog(`Unhandled promise rejection: ${event.reason}`);
51
+ event.preventDefault();
52
+ });
53
+
54
+ // Canvas操作の安全な実行
55
+ function safeExecute(operation, errorMessage = "操作中にエラーが発生しました") {
56
+ try {
57
+ return operation();
58
+ } catch (error) {
59
+ debugLog(`Safe execute error: ${error.message}`);
60
+ if (isCanvasReady()) {
61
+ showCanvasError(errorMessage);
62
+ }
63
+ return null;
64
+ }
65
+ }
66
+ ```
67
+
68
+ ### 3. ネットワークエラー対応
69
+ ```python
70
+ def with_retry(func, max_retries=3):
71
+ """リトライ機能付きデコレータ"""
72
+ def wrapper(*args, **kwargs):
73
+ for attempt in range(max_retries):
74
+ try:
75
+ return func(*args, **kwargs)
76
+ except Exception as e:
77
+ if attempt == max_retries - 1:
78
+ raise e
79
+ time.sleep(1) # 1秒待機してリトライ
80
+ return None
81
+ return wrapper
82
+
83
+ @with_retry
84
+ def download_model():
85
+ """モデルダウンロード(リトライ付き)"""
86
+ # DWPoseモデルダウンロード処理
87
+ pass
88
+ ```
89
+
90
+ ## 完了条件
91
+ - [ ] 各種エラーが適切に分類される
92
+ - [ ] ユーザーに分かりやすいメッセージが表示される
93
+ - [ ] ネットワークエラー時にリトライされる
94
+ - [ ] Canvasエラー時に視覚的にフィードバックされる
issues/020_Gradioトースト通知.md ADDED
@@ -0,0 +1,89 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Issue: Gradioトースト通知
2
+
3
+ ## 概要
4
+ Gradioのトースト通知機能を活用したユーザーフィードバックシステムを実装。
5
+
6
+ ## 実装内容
7
+
8
+ ### 1. 通知レベル別処理
9
+ ```python
10
+ def notify_success(message):
11
+ """成功通知"""
12
+ return gr.Info(message)
13
+
14
+ def notify_warning(message):
15
+ """警告通知"""
16
+ return gr.Warning(message)
17
+
18
+ def notify_error(message):
19
+ """エラー通知"""
20
+ return gr.Error(message)
21
+
22
+ # 使用例
23
+ def handle_image_upload(image):
24
+ try:
25
+ # 処理
26
+ return result, notify_success("画像をアップロードしました")
27
+ except Exception as e:
28
+ return None, notify_error(f"アップロードに失敗しました: {str(e)}")
29
+ ```
30
+
31
+ ### 2. 進捗通知
32
+ ```python
33
+ def notify_progress(message, progress=None):
34
+ """進捗通知"""
35
+ if progress:
36
+ return gr.Progress(progress, desc=message)
37
+ else:
38
+ return gr.Info(message)
39
+
40
+ # DWPose処理時の進捗表示
41
+ def detect_pose_with_progress(image):
42
+ notify_progress("人物を検出中...", 0.3)
43
+ # YOLOX実行
44
+
45
+ notify_progress("ポーズを解析中...", 0.7)
46
+ # DWPose実行
47
+
48
+ notify_progress("完了", 1.0)
49
+ return result
50
+ ```
51
+
52
+ ### 3. JavaScriptからの通知
53
+ ```javascript
54
+ // Gradioトースト通知のトリガー
55
+ window.showToast = function(type, message) {
56
+ // Gradioの隠しコンポーネントを使って通知
57
+ if (window.triggerToast) {
58
+ window.triggerToast(type, message);
59
+ }
60
+ };
61
+
62
+ // Canvas操作時の通知
63
+ function notifyCanvasOperation(message) {
64
+ showToast('info', message);
65
+ }
66
+ ```
67
+
68
+ ### 4. 通知設定の管理
69
+ ```python
70
+ # 通知設定
71
+ NOTIFICATION_SETTINGS = {
72
+ 'show_success': True,
73
+ 'show_warnings': True,
74
+ 'show_errors': True,
75
+ 'auto_dismiss': True,
76
+ 'dismiss_timeout': 3000 # 3秒
77
+ }
78
+
79
+ def configure_notifications(settings):
80
+ """通知設定の更新"""
81
+ global NOTIFICATION_SETTINGS
82
+ NOTIFICATION_SETTINGS.update(settings)
83
+ ```
84
+
85
+ ## 完了条件
86
+ - [ ] 成功/警告/エラーの通知が適切に表示される
87
+ - [ ] 進捗表示が機能する
88
+ - [ ] JavaScriptからも通知できる
89
+ - [ ] 通知が適切なタイミングで消える