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

feat: Enhance pose editor with improved hand data logging and template selection functionality

Browse files

- Added detailed logging for hand data preparation and drawing in pose_editor.js.
- Implemented a new template selection feature that consolidates multiple templates into one with a manual update button.
- Introduced a new JSON file (3heads.json) containing pose data for 3-headed characters.
- Updated poses.json to include structured pose data for 2-head and 3-head characters.
- Improved error handling and user experience during template loading and pose updates.

.gitignore CHANGED
@@ -95,10 +95,10 @@ output/
95
  *.avi
96
  *.mov
97
  *.jsonl
98
- *.json
99
  !template_pose.json
100
  # testプログラム
101
- test_*.py
102
 
103
  # HuggingFace Spacesデプロイ関連
104
  .space/
 
95
  *.avi
96
  *.mov
97
  *.jsonl
98
+ test*.json
99
  !template_pose.json
100
  # testプログラム
101
+ test*.py
102
 
103
  # HuggingFace Spacesデプロイ関連
104
  .space/
3heads.json ADDED
@@ -0,0 +1,477 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "bodies": {
3
+ "candidate": [
4
+ [
5
+ 253.99999999999997,
6
+ 148.66666666666663,
7
+ 1.0,
8
+ 0
9
+ ],
10
+ [
11
+ 256.33333333333337,
12
+ 210.0,
13
+ 1.0,
14
+ 0
15
+ ],
16
+ [
17
+ 205.33333333333334,
18
+ 209.33333333333331,
19
+ 1.0,
20
+ 0
21
+ ],
22
+ [
23
+ 184.66666666666666,
24
+ 261.99999999999994,
25
+ 1.0,
26
+ 0
27
+ ],
28
+ [
29
+ 165.33333333333334,
30
+ 310.6666666666667,
31
+ 1.0,
32
+ 0
33
+ ],
34
+ [
35
+ 307.33333333333337,
36
+ 210.66666666666669,
37
+ 1.0,
38
+ 0
39
+ ],
40
+ [
41
+ 327.33333333333337,
42
+ 262.6666666666667,
43
+ 1.0,
44
+ 0
45
+ ],
46
+ [
47
+ 344.66666666666674,
48
+ 309.99999999999994,
49
+ 1.0,
50
+ 0
51
+ ],
52
+ [
53
+ 223.33333333333334,
54
+ 339.3333333333333,
55
+ 1.0,
56
+ 0
57
+ ],
58
+ [
59
+ 224.66666666666666,
60
+ 399.3333333333334,
61
+ 1.0,
62
+ 0
63
+ ],
64
+ [
65
+ 234.66666666666666,
66
+ 470.00000000000006,
67
+ 1.0,
68
+ 0
69
+ ],
70
+ [
71
+ 286.66666666666674,
72
+ 339.3333333333333,
73
+ 1.0,
74
+ 0
75
+ ],
76
+ [
77
+ 281.33333333333337,
78
+ 400.00000000000006,
79
+ 1.0,
80
+ 0
81
+ ],
82
+ [
83
+ 274.66666666666674,
84
+ 470.00000000000006,
85
+ 1.0,
86
+ 0
87
+ ],
88
+ [
89
+ 233.99999999999997,
90
+ 133.99999999999994,
91
+ 1.0,
92
+ 0
93
+ ],
94
+ [
95
+ 271.33333333333337,
96
+ 133.99999999999994,
97
+ 1.0,
98
+ 0
99
+ ],
100
+ [
101
+ 206.66666666666666,
102
+ 150.66666666666663,
103
+ 1.0,
104
+ 0
105
+ ],
106
+ [
107
+ 298.0,
108
+ 150.66666666666663,
109
+ 1.0,
110
+ 0
111
+ ],
112
+ [
113
+ 228.0,
114
+ 479.33333333333337,
115
+ 1.0,
116
+ 0
117
+ ],
118
+ [
119
+ 283.33333333333337,
120
+ 478.66666666666674,
121
+ 1.0,
122
+ 0
123
+ ]
124
+ ],
125
+ "subset": []
126
+ },
127
+ "hands": [
128
+ [
129
+ 344.66666666666674,
130
+ 311.99999999999994,
131
+ 0.7150347828865051,
132
+ 339.33333333333337,
133
+ 321.99999999999994,
134
+ 0.7231502532958984,
135
+ 333.33333333333337,
136
+ 331.3333333333333,
137
+ 0.661381721496582,
138
+ 331.33333333333337,
139
+ 339.99999999999994,
140
+ 0.6448965668678284,
141
+ 330.0,
142
+ 349.3333333333333,
143
+ 0.591521143913269,
144
+ 350.0,
145
+ 332.6666666666667,
146
+ 0.6919199228286743,
147
+ 354.0,
148
+ 341.99999999999994,
149
+ 0.6912438273429871,
150
+ 358.0,
151
+ 347.99999999999994,
152
+ 0.6884741187095642,
153
+ 361.33333333333337,
154
+ 353.3333333333333,
155
+ 0.705511212348938,
156
+ 357.33333333333337,
157
+ 329.3333333333333,
158
+ 0.7716554403305054,
159
+ 363.33333333333337,
160
+ 339.3333333333333,
161
+ 0.7899695038795471,
162
+ 367.33333333333337,
163
+ 347.3333333333333,
164
+ 0.7758855223655701,
165
+ 370.0,
166
+ 353.99999999999994,
167
+ 0.7775080800056458,
168
+ 363.33333333333337,
169
+ 326.6666666666667,
170
+ 0.789682924747467,
171
+ 369.33333333333337,
172
+ 335.99999999999994,
173
+ 0.7895171046257019,
174
+ 372.0,
175
+ 343.3333333333333,
176
+ 0.7826341390609741,
177
+ 374.0,
178
+ 349.99999999999994,
179
+ 0.7542178630828857,
180
+ 368.66666666666674,
181
+ 323.99999999999994,
182
+ 0.7270801067352295,
183
+ 372.66666666666674,
184
+ 330.6666666666667,
185
+ 0.7413800358772278,
186
+ 376.0,
187
+ 337.99999999999994,
188
+ 0.7292997241020203,
189
+ 378.0,
190
+ 343.99999999999994,
191
+ 0.7108736634254456
192
+ ],
193
+ [
194
+ 165.33333333333334,
195
+ 312.6666666666667,
196
+ 0.7797001600265503,
197
+ 170.66666666666666,
198
+ 321.99999999999994,
199
+ 0.7401540279388428,
200
+ 175.33333333333334,
201
+ 331.99999999999994,
202
+ 0.7077389359474182,
203
+ 173.33333333333334,
204
+ 340.6666666666667,
205
+ 0.7360478043556213,
206
+ 173.33333333333334,
207
+ 347.99999999999994,
208
+ 0.7185596227645874,
209
+ 160.66666666666666,
210
+ 332.6666666666667,
211
+ 0.7588577270507812,
212
+ 155.99999999999997,
213
+ 341.99999999999994,
214
+ 0.7737218141555786,
215
+ 152.66666666666666,
216
+ 347.99999999999994,
217
+ 0.7760618329048157,
218
+ 148.66666666666666,
219
+ 352.6666666666667,
220
+ 0.7934918403625488,
221
+ 153.99999999999997,
222
+ 329.99999999999994,
223
+ 0.8451609015464783,
224
+ 147.99999999999997,
225
+ 339.99999999999994,
226
+ 0.8416674733161926,
227
+ 143.33333333333334,
228
+ 347.3333333333333,
229
+ 0.8654172420501709,
230
+ 139.99999999999997,
231
+ 352.6666666666667,
232
+ 0.829170823097229,
233
+ 147.99999999999997,
234
+ 326.6666666666667,
235
+ 0.8563532829284668,
236
+ 143.33333333333334,
237
+ 335.99999999999994,
238
+ 0.8371527194976807,
239
+ 138.66666666666666,
240
+ 343.3333333333333,
241
+ 0.8210606575012207,
242
+ 134.66666666666666,
243
+ 348.6666666666667,
244
+ 0.7967222929000854,
245
+ 143.99999999999997,
246
+ 323.3333333333333,
247
+ 0.8345107436180115,
248
+ 139.99999999999997,
249
+ 329.3333333333333,
250
+ 0.7522804737091064,
251
+ 135.99999999999997,
252
+ 335.99999999999994,
253
+ 0.7406725883483887,
254
+ 132.66666666666666,
255
+ 341.3333333333333,
256
+ 0.7635241746902466
257
+ ]
258
+ ],
259
+ "faces": [
260
+ [
261
+ 216.66666666666666,
262
+ 135.99999999999994,
263
+ 0.36343619227409363,
264
+ 217.33333333333334,
265
+ 149.33333333333331,
266
+ 0.3816818594932556,
267
+ 219.33333333333334,
268
+ 157.33333333333331,
269
+ 0.42397695779800415,
270
+ 221.99999999999997,
271
+ 165.33333333333331,
272
+ 0.44912877678871155,
273
+ 226.66666666666666,
274
+ 173.99999999999994,
275
+ 0.46479764580726624,
276
+ 231.99999999999997,
277
+ 179.99999999999994,
278
+ 0.42750057578086853,
279
+ 238.66666666666666,
280
+ 182.66666666666663,
281
+ 0.4347080588340759,
282
+ 245.33333333333334,
283
+ 185.33333333333331,
284
+ 0.4085705578327179,
285
+ 253.33333333333334,
286
+ 187.99999999999994,
287
+ 0.39466774463653564,
288
+ 262.0,
289
+ 185.99999999999994,
290
+ 0.4313513934612274,
291
+ 269.33333333333337,
292
+ 182.66666666666663,
293
+ 0.4474577009677887,
294
+ 276.0,
295
+ 180.66666666666663,
296
+ 0.4354158639907837,
297
+ 280.0,
298
+ 173.99999999999994,
299
+ 0.4418158233165741,
300
+ 286.0,
301
+ 165.99999999999994,
302
+ 0.40984541177749634,
303
+ 288.0,
304
+ 155.33333333333331,
305
+ 0.3851367235183716,
306
+ 289.33333333333337,
307
+ 149.99999999999994,
308
+ 0.35478973388671875,
309
+ 290.0,
310
+ 136.66666666666663,
311
+ 0.3434315025806427,
312
+ 221.99999999999997,
313
+ 131.33333333333331,
314
+ 0.3179595470428467,
315
+ 228.66666666666666,
316
+ 129.33333333333331,
317
+ 0.304362028837204,
318
+ 0.0,
319
+ 0.0,
320
+ 0.0,
321
+ 238.66666666666666,
322
+ 123.33333333333331,
323
+ 0.3022729754447937,
324
+ 0.0,
325
+ 0.0,
326
+ 0.0,
327
+ 262.0,
328
+ 123.33333333333331,
329
+ 0.3105626106262207,
330
+ 267.33333333333337,
331
+ 122.66666666666663,
332
+ 0.30489420890808105,
333
+ 272.66666666666674,
334
+ 122.66666666666663,
335
+ 0.30186206102371216,
336
+ 278.0,
337
+ 129.99999999999994,
338
+ 0.30828186869621277,
339
+ 284.66666666666674,
340
+ 131.33333333333331,
341
+ 0.3039014935493469,
342
+ 252.66666666666666,
343
+ 133.33333333333331,
344
+ 0.33224114775657654,
345
+ 253.33333333333334,
346
+ 135.99999999999994,
347
+ 0.35275089740753174,
348
+ 253.99999999999997,
349
+ 141.99999999999994,
350
+ 0.33941948413848877,
351
+ 253.99999999999997,
352
+ 148.66666666666663,
353
+ 0.3312435448169708,
354
+ 246.66666666666666,
355
+ 153.33333333333331,
356
+ 0.331066370010376,
357
+ 250.66666666666666,
358
+ 153.99999999999994,
359
+ 0.33620479702949524,
360
+ 253.33333333333334,
361
+ 154.66666666666663,
362
+ 0.32499319314956665,
363
+ 257.33333333333337,
364
+ 154.66666666666663,
365
+ 0.345694363117218,
366
+ 261.33333333333337,
367
+ 154.66666666666663,
368
+ 0.35932013392448425,
369
+ 227.99999999999997,
370
+ 134.66666666666663,
371
+ 0.35944992303848267,
372
+ 231.99999999999997,
373
+ 132.66666666666663,
374
+ 0.34046822786331177,
375
+ 237.33333333333334,
376
+ 132.66666666666663,
377
+ 0.3438405692577362,
378
+ 243.33333333333334,
379
+ 134.66666666666663,
380
+ 0.34850671887397766,
381
+ 237.33333333333334,
382
+ 135.99999999999994,
383
+ 0.35241225361824036,
384
+ 231.99999999999997,
385
+ 136.66666666666663,
386
+ 0.3446846604347229,
387
+ 264.0,
388
+ 134.66666666666663,
389
+ 0.35195866227149963,
390
+ 268.0,
391
+ 131.99999999999994,
392
+ 0.3443024456501007,
393
+ 272.66666666666674,
394
+ 131.99999999999994,
395
+ 0.34244057536125183,
396
+ 277.33333333333337,
397
+ 134.66666666666663,
398
+ 0.33403435349464417,
399
+ 273.33333333333337,
400
+ 135.99999999999994,
401
+ 0.3441479802131653,
402
+ 268.0,
403
+ 135.99999999999994,
404
+ 0.3523240089416504,
405
+ 241.99999999999997,
406
+ 165.99999999999994,
407
+ 0.3805367350578308,
408
+ 244.66666666666666,
409
+ 162.66666666666663,
410
+ 0.3476058840751648,
411
+ 251.33333333333334,
412
+ 161.33333333333331,
413
+ 0.3350098133087158,
414
+ 253.33333333333334,
415
+ 161.99999999999994,
416
+ 0.33215898275375366,
417
+ 255.99999999999997,
418
+ 161.99999999999994,
419
+ 0.3394817113876343,
420
+ 262.66666666666674,
421
+ 162.66666666666663,
422
+ 0.3805198073387146,
423
+ 267.33333333333337,
424
+ 165.33333333333331,
425
+ 0.38708436489105225,
426
+ 264.66666666666663,
427
+ 169.99999999999994,
428
+ 0.3987838327884674,
429
+ 260.0,
430
+ 172.66666666666663,
431
+ 0.39329320192337036,
432
+ 253.99999999999997,
433
+ 173.33333333333331,
434
+ 0.3859204947948456,
435
+ 249.33333333333334,
436
+ 171.99999999999994,
437
+ 0.37775635719299316,
438
+ 244.66666666666666,
439
+ 169.33333333333331,
440
+ 0.38992202281951904,
441
+ 243.33333333333334,
442
+ 165.99999999999994,
443
+ 0.37453481554985046,
444
+ 247.99999999999997,
445
+ 163.33333333333331,
446
+ 0.3382393717765808,
447
+ 253.33333333333334,
448
+ 163.33333333333331,
449
+ 0.33168619871139526,
450
+ 260.66666666666674,
451
+ 163.33333333333331,
452
+ 0.35072460770606995,
453
+ 266.0,
454
+ 165.33333333333331,
455
+ 0.3812493085861206,
456
+ 261.33333333333337,
457
+ 168.66666666666663,
458
+ 0.4090779423713684,
459
+ 253.99999999999997,
460
+ 168.66666666666663,
461
+ 0.39178723096847534,
462
+ 248.66666666666666,
463
+ 168.66666666666663,
464
+ 0.3850829601287842
465
+ ]
466
+ ],
467
+ "resolution": [
468
+ 512,
469
+ 512
470
+ ],
471
+ "metadata": {
472
+ "format": "dwpose-editor",
473
+ "version": "1.0",
474
+ "exported_at": "2025-06-13T08:21:24",
475
+ "description": "2頭身・3頭身キャラクター用ポーズデータ"
476
+ }
477
+ }
app.py CHANGED
@@ -69,12 +69,14 @@ def main():
69
  # テンプレートポーズ選択
70
  template_dropdown = gr.Dropdown(
71
  label="テンプレートポーズ",
72
- choices=[
73
- "2頭身立ちポーズ",
74
- "3頭身立ちポーズ",
75
- "2頭身座りポーズ"
76
- ],
77
- value="2頭身立ちポーズ"
 
 
78
  )
79
 
80
  # JSONファイルアップロード
@@ -282,49 +284,107 @@ def main():
282
  global _current_poses, _current_frame_index
283
 
284
  try:
285
- templates_path = os.path.join(os.path.dirname(__file__), "templates", "poses.json")
286
- with open(templates_path, "r", encoding="utf-8") as f:
287
- templates = json.load(f)
288
-
289
- # テンプレート名をキーに変換
290
- template_key_map = {
291
- "2頭身立ちポーズ": "2_head_standing",
292
- "3頭身立ちポーズ": "3_head_standing",
293
- "2頭身座りポーズ": "2_head_sitting"
294
- }
295
-
296
- template_key = template_key_map.get(template_name)
297
- if template_key and template_key in templates["poses"]:
298
- pose_data = templates["poses"][template_key]["data"]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
299
 
300
- # refsと同じ_current_poses形式で保存
301
- if pose_data:
302
- # people形式に変換してから保存
303
- person_data = {
304
- "pose_keypoints_2d": [],
305
- "hand_left_keypoints_2d": pose_data.get('hands', [[], []])[0] if pose_data.get('hands') else [],
306
- "hand_right_keypoints_2d": pose_data.get('hands', [[], []])[1] if pose_data.get('hands') and len(pose_data['hands']) > 1 else [],
307
- "face_keypoints_2d": pose_data.get('faces', [[]])[0] if pose_data.get('faces') else []
308
  }
309
-
310
- # bodies.candidateからpose_keypoints_2d変換
311
- if 'bodies' in pose_data and 'candidate' in pose_data['bodies']:
312
- candidates = pose_data['bodies']['candidate']
313
- for candidate in candidates:
314
- if candidate and len(candidate) >= 2:
315
- person_data["pose_keypoints_2d"].extend([candidate[0], candidate[1], candidate[2] if len(candidate) > 2 else 1.0])
316
-
317
- _current_poses = [{
318
- 'people': [person_data],
319
- 'metadata': {
320
- 'resolution': pose_data.get('resolution', [512, 512])
321
- }
322
- }]
323
- _current_frame_index = 0
324
- print(f"[DEBUG] ✅ グローバル変数更新完了(テンプレート読み込み・refs互換): {template_name}")
 
325
 
326
  notify_success(f"{template_name}を読み込みました")
327
- return pose_data, pose_data
328
  else:
329
  notify_error("テンプレートが見つかりません")
330
  return None, {}
@@ -754,8 +814,8 @@ def main():
754
  outputs=[js_executor]
755
  )
756
 
757
- # テンプレートポーズ選択イベント
758
- template_dropdown.change(
759
  fn=load_template_pose,
760
  inputs=[template_dropdown],
761
  outputs=[output_json, pose_data]
 
69
  # テンプレートポーズ選択
70
  template_dropdown = gr.Dropdown(
71
  label="テンプレートポーズ",
72
+ choices=["3頭身立ちポーズ"],
73
+ value="3頭身立ちポーズ"
74
+ )
75
+
76
+ # ポーズ更新ボタン
77
+ template_update_btn = gr.Button(
78
+ "🔄 ポーズ更新",
79
+ variant="secondary"
80
  )
81
 
82
  # JSONファイルアップロード
 
284
  global _current_poses, _current_frame_index
285
 
286
  try:
287
+ # 3頭身テンプレートの場合は3heads.jsonを使用
288
+ if template_name == "3頭身立ちポーズ":
289
+ templates_path = os.path.join(os.path.dirname(__file__), "3heads.json")
290
+ with open(templates_path, "r", encoding="utf-8") as f:
291
+ pose_data = json.load(f)
292
+ else:
293
+ # 従来のテンプレート処理
294
+ templates_path = os.path.join(os.path.dirname(__file__), "templates", "poses.json")
295
+ with open(templates_path, "r", encoding="utf-8") as f:
296
+ templates = json.load(f)
297
+
298
+ # テンプレート名をキーに変換
299
+ template_key_map = {
300
+ "2頭身立ちポーズ": "2_head_standing",
301
+ "2頭身座りポーズ": "2_head_sitting"
302
+ }
303
+
304
+ template_key = template_key_map.get(template_name)
305
+ if template_key and template_key in templates["poses"]:
306
+ pose_data = templates["poses"][template_key]["data"]
307
+ else:
308
+ notify_error("テンプレートが見つかりません")
309
+ return None, {}
310
+
311
+ if pose_data:
312
+ # 3heads.jsonの場合は直接フォーマット、templates/poses.jsonの場合は.dataアクセス
313
+ if template_name == "3頭身立ちポーズ":
314
+ # 3heads.jsonは直接DWPoseフォーマット
315
+ actual_pose_data = pose_data
316
+ else:
317
+ # templates/poses.jsonの場合は.dataアクセス
318
+ actual_pose_data = pose_data
319
+
320
+ # people形式に変換してから保存
321
+ person_data = {
322
+ "pose_keypoints_2d": [],
323
+ "hand_left_keypoints_2d": actual_pose_data.get('hands', [[], []])[0] if actual_pose_data.get('hands') else [],
324
+ "hand_right_keypoints_2d": actual_pose_data.get('hands', [[], []])[1] if actual_pose_data.get('hands') and len(actual_pose_data['hands']) > 1 else [],
325
+ "face_keypoints_2d": actual_pose_data.get('faces', [[]])[0] if actual_pose_data.get('faces') else []
326
+ }
327
+
328
+ # デバッグ:手データの変換確認
329
+ print(f"[DEBUG] 🫳 Hand data conversion:")
330
+ print(f" - hands in data: {bool(actual_pose_data.get('hands'))}")
331
+ print(f" - hands length: {len(actual_pose_data.get('hands', []))}")
332
+ if actual_pose_data.get('hands'):
333
+ print(f" - left hand length: {len(actual_pose_data['hands'][0]) if len(actual_pose_data['hands']) > 0 else 0}")
334
+ print(f" - right hand length: {len(actual_pose_data['hands'][1]) if len(actual_pose_data['hands']) > 1 else 0}")
335
+ print(f" - person_data left hand: {len(person_data['hand_left_keypoints_2d'])}")
336
+ print(f" - person_data right hand: {len(person_data['hand_right_keypoints_2d'])}")
337
+
338
+ # デバッグ:actual_pose_dataの構造確認
339
+ print(f"[DEBUG] 🔍 actual_pose_data structure check:")
340
+ print(f" - keys: {list(actual_pose_data.keys())}")
341
+ print(f" - has bodies: {'bodies' in actual_pose_data}")
342
+ if 'bodies' in actual_pose_data:
343
+ print(f" - bodies keys: {list(actual_pose_data['bodies'].keys())}")
344
+ print(f" - has candidate: {'candidate' in actual_pose_data['bodies']}")
345
+
346
+ # bodies.candidateからpose_keypoints_2d変換
347
+ if 'bodies' in actual_pose_data and 'candidate' in actual_pose_data['bodies']:
348
+ candidates = actual_pose_data['bodies']['candidate']
349
+ print(f"[DEBUG] 🤖 Body data conversion:")
350
+ print(f" - bodies.candidate length: {len(candidates)}")
351
+ for i, candidate in enumerate(candidates):
352
+ if candidate and len(candidate) >= 2:
353
+ person_data["pose_keypoints_2d"].extend([candidate[0], candidate[1], candidate[2] if len(candidate) > 2 else 1.0])
354
+ if i < 3: # 最初の3つだけログ出力
355
+ print(f" - candidate {i}: {candidate[:3]}")
356
+ print(f" - final pose_keypoints_2d length: {len(person_data['pose_keypoints_2d'])}")
357
+ else:
358
+ print(f"[DEBUG] ❌ No bodies.candidate found in actual_pose_data!")
359
+ print(f" - has bodies: {bool(actual_pose_data.get('bodies'))}")
360
+ if actual_pose_data.get('bodies'):
361
+ print(f" - bodies keys: {list(actual_pose_data['bodies'].keys())}")
362
 
363
+ _current_poses = [{
364
+ 'people': [person_data],
365
+ 'metadata': {
366
+ 'resolution': actual_pose_data.get('resolution', [512, 512])
 
 
 
 
367
  }
368
+ }]
369
+ _current_frame_index = 0
370
+ print(f"[DEBUG] グローバル変数更新完了(テンプレート読み込み・refs互換): {template_name}")
371
+
372
+ # people形式でデータを構築(元のbodies/hands/facesも保持)
373
+ people_format_data = {
374
+ 'people': [person_data],
375
+ 'bodies': actual_pose_data.get('bodies', {}), # 元のbodiesデータも保持
376
+ 'hands': actual_pose_data.get('hands', []), # 元のhandsデータも保持
377
+ 'faces': actual_pose_data.get('faces', []), # 元のfacesデータも保持
378
+ 'metadata': {
379
+ 'resolution': actual_pose_data.get('resolution', [512, 512])
380
+ },
381
+ 'is_template_load': True # テンプレート読み込みフラグ
382
+ }
383
+
384
+ print(f"[DEBUG] 🔄 Returning people format data with {len(person_data['hand_left_keypoints_2d'])} left hand points")
385
 
386
  notify_success(f"{template_name}を読み込みました")
387
+ return people_format_data, people_format_data
388
  else:
389
  notify_error("テンプレートが見つかりません")
390
  return None, {}
 
814
  outputs=[js_executor]
815
  )
816
 
817
+ # ポーズ更新ボタンクリックイベント
818
+ template_update_btn.click(
819
  fn=load_template_pose,
820
  inputs=[template_dropdown],
821
  outputs=[output_json, pose_data]
issues/039_テンプレート選択機能改善.md ADDED
@@ -0,0 +1,136 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Issue #039: テンプレート選択機能改善 🎯✨
2
+
3
+ ## 概要
4
+ 現在のテンプレートドロップダウンには3つのテンプレートが登録されているが、これを1つに集約し、「ポーズ更新」ボタンを追加してユーザーが任意のタイミングでテンプレートを反映できる機能を実装する。
5
+
6
+ ## 問題の詳細
7
+
8
+ ### 現在の状況
9
+ - テンプレートドロップダウンに3つのテンプレートが登録済み
10
+ - "2頭身立ちポーズ"
11
+ - "3頭身立ちポーズ"
12
+ - "2頭身座りポーズ"
13
+ - 3heads.jsonに3頭身テンプレートデータが用意済み
14
+ - テンプレート選択時に自動反映される仕様
15
+
16
+ ### 改善したい点
17
+ 1. **テンプレート数の集約**: 現在の3つから1つに削減
18
+ 2. **手動更新方式**: 自動反映ではなく、「ポーズ更新」ボタンによる手動更新
19
+ 3. **テンプレートファイル活用**: 3heads.jsonの活用
20
+
21
+ ## 解決方法
22
+
23
+ ### 1. テンプレートリストの変更
24
+ ```python
25
+ # Before
26
+ template_dropdown = gr.Dropdown(
27
+ label="テンプレートポーズ",
28
+ choices=[
29
+ "2頭身立ちポーズ",
30
+ "3頭身立ちポーズ",
31
+ "2頭身座りポーズ"
32
+ ],
33
+ value="2頭身立ちポーズ"
34
+ )
35
+
36
+ # After
37
+ template_dropdown = gr.Dropdown(
38
+ label="テンプレートポーズ",
39
+ choices=["3頭身立ちポーズ"],
40
+ value="3頭身立ちポーズ"
41
+ )
42
+ ```
43
+
44
+ ### 2. ポーズ更新ボタンの追加
45
+ テンプレートドロップダウンの直下に「ポーズ更新」ボタンを配置:
46
+ ```python
47
+ template_update_btn = gr.Button(
48
+ "🔄 ポーズ更新",
49
+ variant="secondary",
50
+ scale=0
51
+ )
52
+ ```
53
+
54
+ ### 3. イベントハンドラーの変更
55
+ - 既存の `template_dropdown.change` イベントを削除
56
+ - 新しい `template_update_btn.click` イベントを追加
57
+
58
+ ### 4. テンプレートファイルの統合
59
+ - 3heads.jsonを適切に読み込む処理を実装
60
+ - テンプレート名とファイルパスのマッピングを更新
61
+
62
+ ## 実装詳細
63
+
64
+ ### Phase 1: UIコンポーネントの変更
65
+ 1. `app.py:70-78` テンプレートドロップダウンの選択肢を1つに変更
66
+ 2. `app.py:78` の直後に「ポーズ更新」ボタンを追加
67
+
68
+ ### Phase 2: イベントハンドラーの変更
69
+ 1. `app.py:758-761` 既存のテンプレート自動更新イベントを削除
70
+ 2. 新しいボタンクリックイベントを追加
71
+ 3. `on_template_load()` 関数は既存のものを流用
72
+
73
+ ### Phase 3: テンプレートファイル処理の修正
74
+ 1. 3heads.jsonの読み込み処理を確認
75
+ 2. テンプレート名とファイルパスのマッピングを更新
76
+ 3. "3頭身立ちポーズ" → `3heads.json` の対応付け
77
+
78
+ ## 期待される効果
79
+
80
+ ### ユーザーエクスペリエンス
81
+ - **シンプルな選択肢**: 1つのテンプレートで迷わない
82
+ - **手動制御**: ユーザーが任意のタイミングで更新可能
83
+ - **明確な操作**: ボタンクリックで何が起こるかが明確
84
+
85
+ ### 技術的メリット
86
+ - **コード簡素化**: テンプレート管理が単純化
87
+ - **ファイル活用**: 実際のテンプレートファイルを有効活用
88
+ - **デバッグ容易性**: 手動トリガーでデバッグしやすい
89
+
90
+ ## テスト項目
91
+
92
+ ### 基本機能テスト
93
+ - [ ] テンプレートドロップダウンに「3頭身立ちポーズ」のみ表示
94
+ - [ ] 「ポーズ更新」ボタンが正常に表示される
95
+ - [ ] ボタンクリックで3heads.jsonのポーズデータが読み込まれる
96
+ - [ ] Canvas上にポーズが正常に描画される
97
+
98
+ ### エラーハンドリング
99
+ - [ ] 3heads.jsonが存在しない場合のエラー処理
100
+ - [ ] JSONファイルが不正な場合のエラー処理
101
+ - [ ] Canvas更新失敗時のエラー処理
102
+
103
+ ### UI/UX確認
104
+ - [ ] ボタンの配置とデザインが適切
105
+ - [ ] トースト通知が正常に表示される
106
+ - [ ] レスポンシブデザインの確認
107
+
108
+ ## 参考実装
109
+
110
+ ### refs/dwpose_modifier参照点
111
+ - `refs/dwpose_modifier/app.py` テンプレート選択UI実装
112
+ - `refs/dwpose_modifier/static/pose_editor.js` Canvas更新処理
113
+ - `refs/dwpose_modifier/utils/template_loader.py` テンプレート読み込み処理
114
+
115
+ ### 既存コード活用
116
+ - `on_template_load()` 関数 (app.py:281)
117
+ - `update_canvas_with_pose()` 処理
118
+ - エラーハンドリング (`utils/notifications.py`)
119
+
120
+ ## 実装工数
121
+ - **見積もり**: 2-3時間
122
+ - **優先度**: Medium
123
+ - **複雑度**: Low-Medium
124
+
125
+ ## 完了条件
126
+ 1. テンプレートドロップダウンが1つの選択肢のみ表示される
127
+ 2. 「ポーズ更新」ボタンが適切に配置・動作する
128
+ 3. 3heads.jsonのテンプレートデータが正常に読み込まれる
129
+ 4. Canvas上にポーズが正確に描画される
130
+ 5. エラーハンドリングが適切に動作する
131
+
132
+ ---
133
+ *Created: 2025-01-15*
134
+ *Status: 新規作成*
135
+ *Assignee: Claude Code Agent*
136
+ *Tags: UI改善, テンプ��ート機能, ユーザビリティ*
static/pose_editor.js CHANGED
@@ -1876,14 +1876,23 @@ function drawPose(poseData, enableHands = true, enableFace = true, highlightInde
1876
  person.hand_left_keypoints_2d || [],
1877
  person.hand_right_keypoints_2d || []
1878
  ];
 
 
 
 
 
 
1879
  // 💖 people形式のみサポート、古いhands形式は削除
1880
  } else {
1881
  handsDataForDrawing = [[], []]; // 空の手データ
 
1882
  }
1883
 
1884
  if (handsDataForDrawing && handsDataForDrawing.length >= 2) {
1885
  drawHands(handsDataForDrawing, originalRes, scaleX, scaleY);
1886
  debugLog(`🫳 Hands drawn: left=${handsDataForDrawing[0].length}, right=${handsDataForDrawing[1].length}`);
 
 
1887
  }
1888
  } catch (error) {
1889
  console.error("❌ Error drawing hands:", error);
@@ -2363,9 +2372,20 @@ window.gradioCanvasUpdate = function(pose_json_str) {
2363
  hasBodies: !!poseData?.bodies,
2364
  hasHands: !!poseData?.hands,
2365
  hasFaces: !!poseData?.faces,
2366
- hasBackgroundImage: !!poseData?.background_image
 
 
 
2367
  });
2368
 
 
 
 
 
 
 
 
 
2369
  // 🎨 背景画像が含まれている場合は設定
2370
  if (poseData && poseData.background_image) {
2371
  if (window.setBackgroundImage) {
@@ -2373,9 +2393,28 @@ window.gradioCanvasUpdate = function(pose_json_str) {
2373
  }
2374
  }
2375
 
2376
- // 💥 既存の手・顔データを保護!Python側データで上書きしない
 
2377
  const existingPoseData = window.poseEditorGlobals.poseData;
2378
- if (existingPoseData && existingPoseData.people && existingPoseData.people[0] &&
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2379
  poseData && (!poseData.people || !poseData.people[0])) {
2380
 
2381
  console.log('🛡️ [gradioCanvasUpdate] Protecting existing people data from overwrite');
@@ -2461,6 +2500,14 @@ window.gradioCanvasUpdate = function(pose_json_str) {
2461
 
2462
  // 🔧 グローバル設定で描画(手・顔表示設定を反映)
2463
  if (poseData && Object.keys(poseData).length > 0) {
 
 
 
 
 
 
 
 
2464
  drawPose(
2465
  poseData,
2466
  window.poseEditorGlobals.enableHands,
 
1876
  person.hand_left_keypoints_2d || [],
1877
  person.hand_right_keypoints_2d || []
1878
  ];
1879
+ console.log('🫳 [HANDS DEBUG] Hand data prepared:', {
1880
+ leftLength: handsDataForDrawing[0].length,
1881
+ rightLength: handsDataForDrawing[1].length,
1882
+ leftSample: handsDataForDrawing[0].slice(0, 9),
1883
+ rightSample: handsDataForDrawing[1].slice(0, 9)
1884
+ });
1885
  // 💖 people形式のみサポート、古いhands形式は削除
1886
  } else {
1887
  handsDataForDrawing = [[], []]; // 空の手データ
1888
+ console.log('🫳 [HANDS DEBUG] No people data, using empty hands');
1889
  }
1890
 
1891
  if (handsDataForDrawing && handsDataForDrawing.length >= 2) {
1892
  drawHands(handsDataForDrawing, originalRes, scaleX, scaleY);
1893
  debugLog(`🫳 Hands drawn: left=${handsDataForDrawing[0].length}, right=${handsDataForDrawing[1].length}`);
1894
+ } else {
1895
+ console.log('🫳 [HANDS DEBUG] Invalid handsDataForDrawing structure');
1896
  }
1897
  } catch (error) {
1898
  console.error("❌ Error drawing hands:", error);
 
2372
  hasBodies: !!poseData?.bodies,
2373
  hasHands: !!poseData?.hands,
2374
  hasFaces: !!poseData?.faces,
2375
+ hasBackgroundImage: !!poseData?.background_image,
2376
+ handLeftLength: poseData?.people?.[0]?.hand_left_keypoints_2d?.length || 0,
2377
+ handRightLength: poseData?.people?.[0]?.hand_right_keypoints_2d?.length || 0,
2378
+ isTemplateLoad: !!poseData?.is_template_load
2379
  });
2380
 
2381
+ // 🔍 テンプレート読み込み時の詳細ログ
2382
+ if (poseData?.is_template_load) {
2383
+ console.log('🔄 [TEMPLATE LOAD] Detailed hand data check:', {
2384
+ leftHandData: poseData?.people?.[0]?.hand_left_keypoints_2d?.slice(0, 9) || 'NO DATA',
2385
+ rightHandData: poseData?.people?.[0]?.hand_right_keypoints_2d?.slice(0, 9) || 'NO DATA'
2386
+ });
2387
+ }
2388
+
2389
  // 🎨 背景画像が含まれている場合は設定
2390
  if (poseData && poseData.background_image) {
2391
  if (window.setBackgroundImage) {
 
2393
  }
2394
  }
2395
 
2396
+ // 💥 テンプレート読み込み時は完全置換、それ以外は既存データ保護
2397
+ const isTemplateLoad = poseData.is_template_load === true;
2398
  const existingPoseData = window.poseEditorGlobals.poseData;
2399
+
2400
+ if (isTemplateLoad) {
2401
+ console.log('🔄 [gradioCanvasUpdate] Template load detected - complete replacement');
2402
+ // テンプレート読み込み時は完全置換
2403
+ window.poseEditorGlobals.poseData = poseData;
2404
+ // baseOriginalKeypointsもリセット(新しいテンプレート用)
2405
+ window.poseEditorGlobals.baseOriginalKeypoints = null;
2406
+
2407
+ // 🔧 テンプレート読み込み時は現在のチェックボックス状態を強制取得
2408
+ updateDisplaySettingsFromCheckbox();
2409
+ console.log('🔧 [TEMPLATE LOAD] Forced display settings update:', {
2410
+ enableHands: window.poseEditorGlobals.enableHands,
2411
+ enableFace: window.poseEditorGlobals.enableFace,
2412
+ editMode: window.poseEditorGlobals.editMode
2413
+ });
2414
+
2415
+ // 念のため、チェックボックス監視も再設定
2416
+ setTimeout(() => setupGradioCheckboxListeners(), 100);
2417
+ } else if (existingPoseData && existingPoseData.people && existingPoseData.people[0] &&
2418
  poseData && (!poseData.people || !poseData.people[0])) {
2419
 
2420
  console.log('🛡️ [gradioCanvasUpdate] Protecting existing people data from overwrite');
 
2500
 
2501
  // 🔧 グローバル設定で描画(手・顔表示設定を反映)
2502
  if (poseData && Object.keys(poseData).length > 0) {
2503
+ console.log('🎨 [DRAW CALL] About to call drawPose with settings:', {
2504
+ enableHands: window.poseEditorGlobals.enableHands,
2505
+ enableFace: window.poseEditorGlobals.enableFace,
2506
+ editMode: window.poseEditorGlobals.editMode,
2507
+ hasPeopleData: !!poseData.people,
2508
+ handDataAvailable: !!(poseData.people?.[0]?.hand_left_keypoints_2d?.length > 0)
2509
+ });
2510
+
2511
  drawPose(
2512
  poseData,
2513
  window.poseEditorGlobals.enableHands,
templates/poses.json ADDED
@@ -0,0 +1,109 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "poses": {
3
+ "2_head_standing": {
4
+ "name": "2頭身立ちポーズ",
5
+ "description": "2頭身キャラクター用基本立ちポーズ",
6
+ "data": {
7
+ "bodies": {
8
+ "candidate": [
9
+ [320, 100], [300, 120], [280, 140], [260, 160], [240, 180],
10
+ [340, 120], [360, 140], [380, 160], [400, 180],
11
+ [310, 200], [305, 250], [300, 300],
12
+ [330, 200], [335, 250], [340, 300],
13
+ [320, 80], [320, 60]
14
+ ],
15
+ "subset": [[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 1.0, 17]]
16
+ },
17
+ "hands": [
18
+ [280, 160, 1.0, 285, 165, 1.0, 290, 170, 1.0, 295, 175, 1.0, 300, 180, 1.0,
19
+ 275, 170, 1.0, 280, 175, 1.0, 285, 180, 1.0, 290, 185, 1.0,
20
+ 270, 175, 1.0, 275, 180, 1.0, 280, 185, 1.0, 285, 190, 1.0,
21
+ 265, 180, 1.0, 270, 185, 1.0, 275, 190, 1.0, 280, 195, 1.0,
22
+ 260, 185, 1.0, 265, 190, 1.0, 270, 195, 1.0],
23
+ [380, 160, 1.0, 375, 165, 1.0, 370, 170, 1.0, 365, 175, 1.0, 360, 180, 1.0,
24
+ 385, 170, 1.0, 380, 175, 1.0, 375, 180, 1.0, 370, 185, 1.0,
25
+ 390, 175, 1.0, 385, 180, 1.0, 380, 185, 1.0, 375, 190, 1.0,
26
+ 395, 180, 1.0, 390, 185, 1.0, 385, 190, 1.0, 380, 195, 1.0,
27
+ 400, 185, 1.0, 395, 190, 1.0, 390, 195, 1.0]
28
+ ],
29
+ "faces": [
30
+ [320, 60, 1.0, 315, 65, 1.0, 310, 70, 1.0, 315, 75, 1.0, 320, 80, 1.0,
31
+ 325, 75, 1.0, 330, 70, 1.0, 325, 65, 1.0, 320, 70, 1.0, 318, 72, 1.0,
32
+ 322, 72, 1.0, 320, 75, 1.0, 315, 80, 1.0, 320, 85, 1.0, 325, 80, 1.0,
33
+ 310, 85, 1.0, 315, 90, 1.0, 320, 95, 1.0, 325, 90, 1.0, 330, 85, 1.0]
34
+ ],
35
+ "resolution": [640, 640]
36
+ }
37
+ },
38
+ "3_head_standing": {
39
+ "name": "3頭身立ちポーズ",
40
+ "description": "3頭身キャラクター用基本立ちポーズ",
41
+ "data": {
42
+ "bodies": {
43
+ "candidate": [
44
+ [320, 120], [300, 140], [280, 160], [260, 180], [240, 200],
45
+ [340, 140], [360, 160], [380, 180], [400, 200],
46
+ [310, 250], [305, 350], [300, 450],
47
+ [330, 250], [335, 350], [340, 450],
48
+ [320, 100], [320, 80]
49
+ ],
50
+ "subset": [[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 1.0, 17]]
51
+ },
52
+ "hands": [
53
+ [280, 160, 1.0, 285, 165, 1.0, 290, 170, 1.0, 295, 175, 1.0, 300, 180, 1.0,
54
+ 275, 170, 1.0, 280, 175, 1.0, 285, 180, 1.0, 290, 185, 1.0,
55
+ 270, 175, 1.0, 275, 180, 1.0, 280, 185, 1.0, 285, 190, 1.0,
56
+ 265, 180, 1.0, 270, 185, 1.0, 275, 190, 1.0, 280, 195, 1.0,
57
+ 260, 185, 1.0, 265, 190, 1.0, 270, 195, 1.0],
58
+ [380, 160, 1.0, 375, 165, 1.0, 370, 170, 1.0, 365, 175, 1.0, 360, 180, 1.0,
59
+ 385, 170, 1.0, 380, 175, 1.0, 375, 180, 1.0, 370, 185, 1.0,
60
+ 390, 175, 1.0, 385, 180, 1.0, 380, 185, 1.0, 375, 190, 1.0,
61
+ 395, 180, 1.0, 390, 185, 1.0, 385, 190, 1.0, 380, 195, 1.0,
62
+ 400, 185, 1.0, 395, 190, 1.0, 390, 195, 1.0]
63
+ ],
64
+ "faces": [
65
+ [320, 60, 1.0, 315, 65, 1.0, 310, 70, 1.0, 315, 75, 1.0, 320, 80, 1.0,
66
+ 325, 75, 1.0, 330, 70, 1.0, 325, 65, 1.0, 320, 70, 1.0, 318, 72, 1.0,
67
+ 322, 72, 1.0, 320, 75, 1.0, 315, 80, 1.0, 320, 85, 1.0, 325, 80, 1.0,
68
+ 310, 85, 1.0, 315, 90, 1.0, 320, 95, 1.0, 325, 90, 1.0, 330, 85, 1.0]
69
+ ],
70
+ "resolution": [640, 640]
71
+ }
72
+ },
73
+ "2_head_sitting": {
74
+ "name": "2頭身座りポーズ",
75
+ "description": "2頭身キャラクター用座りポーズ",
76
+ "data": {
77
+ "bodies": {
78
+ "candidate": [
79
+ [320, 150], [300, 170], [280, 190], [260, 210], [240, 230],
80
+ [340, 170], [360, 190], [380, 210], [400, 230],
81
+ [310, 220], [290, 250], [270, 280],
82
+ [330, 220], [350, 250], [370, 280],
83
+ [320, 130], [320, 110]
84
+ ],
85
+ "subset": [[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 1.0, 17]]
86
+ },
87
+ "hands": [
88
+ [280, 160, 1.0, 285, 165, 1.0, 290, 170, 1.0, 295, 175, 1.0, 300, 180, 1.0,
89
+ 275, 170, 1.0, 280, 175, 1.0, 285, 180, 1.0, 290, 185, 1.0,
90
+ 270, 175, 1.0, 275, 180, 1.0, 280, 185, 1.0, 285, 190, 1.0,
91
+ 265, 180, 1.0, 270, 185, 1.0, 275, 190, 1.0, 280, 195, 1.0,
92
+ 260, 185, 1.0, 265, 190, 1.0, 270, 195, 1.0],
93
+ [380, 160, 1.0, 375, 165, 1.0, 370, 170, 1.0, 365, 175, 1.0, 360, 180, 1.0,
94
+ 385, 170, 1.0, 380, 175, 1.0, 375, 180, 1.0, 370, 185, 1.0,
95
+ 390, 175, 1.0, 385, 180, 1.0, 380, 185, 1.0, 375, 190, 1.0,
96
+ 395, 180, 1.0, 390, 185, 1.0, 385, 190, 1.0, 380, 195, 1.0,
97
+ 400, 185, 1.0, 395, 190, 1.0, 390, 195, 1.0]
98
+ ],
99
+ "faces": [
100
+ [320, 60, 1.0, 315, 65, 1.0, 310, 70, 1.0, 315, 75, 1.0, 320, 80, 1.0,
101
+ 325, 75, 1.0, 330, 70, 1.0, 325, 65, 1.0, 320, 70, 1.0, 318, 72, 1.0,
102
+ 322, 72, 1.0, 320, 75, 1.0, 315, 80, 1.0, 320, 85, 1.0, 325, 80, 1.0,
103
+ 310, 85, 1.0, 315, 90, 1.0, 320, 95, 1.0, 325, 90, 1.0, 330, 85, 1.0]
104
+ ],
105
+ "resolution": [640, 640]
106
+ }
107
+ }
108
+ }
109
+ }