charSLee013 commited on
Commit
4b828b4
·
1 Parent(s): ba0c31f

Add example generation script and sample images with LFS support

Browse files

- Add generate_examples.py script for creating example outputs
- Include character figure input examples (4 JPEG images via LFS)
- Include 3D figure input examples (2 JPEG images via LFS)
- Add generated result images for both character and 3D figure examples (via LFS)
- Update app.py, config.py, examples_config.py, and models.py with new features
- Configure .gitattributes to handle JPEG files with Git LFS

.gitattributes CHANGED
@@ -34,4 +34,5 @@ saved_model/**/* filter=lfs diff=lfs merge=lfs -text
34
  *.zst filter=lfs diff=lfs merge=lfs -text
35
  *tfevents* filter=lfs diff=lfs merge=lfs -text
36
  *.jpg filter=lfs diff=lfs merge=lfs -text
 
37
  *.png filter=lfs diff=lfs merge=lfs -text
 
34
  *.zst filter=lfs diff=lfs merge=lfs -text
35
  *tfevents* filter=lfs diff=lfs merge=lfs -text
36
  *.jpg filter=lfs diff=lfs merge=lfs -text
37
+ *.jpeg filter=lfs diff=lfs merge=lfs -text
38
  *.png filter=lfs diff=lfs merge=lfs -text
app.py CHANGED
The diff for this file is too large to render. See raw diff
 
config.py CHANGED
@@ -6,16 +6,16 @@ from dotenv import load_dotenv
6
  load_dotenv()
7
 
8
  class AppConfig(BaseModel):
9
- API_BASE_URL: str = Field(default=os.getenv("API_BASE_URL", "http://localhost:8000"), description="Backend API base URL")
10
  API_AUTH_TOKEN: str = Field(default=os.getenv("API_AUTH_TOKEN", ""), description="API authentication token - REQUIRED")
11
  API_TIMEOUT: int = Field(default=int(os.getenv("API_TIMEOUT", "120")), description="API request timeout in seconds")
12
 
13
  def model_post_init(self, __context) -> None:
14
 
15
  if not self.API_AUTH_TOKEN:
16
- raise ValueError("❌ API_AUTH_TOKEN 环境变量必须设置")
17
 
18
- # 简化的禁用令牌检查
19
  forbidden_tokens = {
20
  "",
21
  "your_secret_token_here",
@@ -23,11 +23,11 @@ class AppConfig(BaseModel):
23
  }
24
 
25
  if self.API_AUTH_TOKEN in forbidden_tokens:
26
- raise ValueError(f"❌ 禁止使用占位符令牌: {self.API_AUTH_TOKEN}")
27
 
28
- # 简化:只要求基本长度
29
  if len(self.API_AUTH_TOKEN) < 8:
30
- raise ValueError("❌ 认证令牌长度必须至少8字符")
31
 
32
  # Polling Configuration
33
  POLL_INTERVAL: float = Field(default=3.0, description="Status polling interval in seconds")
@@ -58,5 +58,7 @@ def get_api_headers() -> dict[str, str]:
58
  }
59
 
60
  def get_api_url(endpoint: str) -> str:
61
-
62
- return f"{CONFIG.API_BASE_URL.rstrip('/')}/{endpoint.lstrip('/')}"
 
 
 
6
  load_dotenv()
7
 
8
  class AppConfig(BaseModel):
9
+ API_BASE_URL: str = Field(default=os.getenv("API_BASE_URL", "https://1251722089-fabl8rzpod.ap-singapore.tencentscf.com"), description="Backend API base URL")
10
  API_AUTH_TOKEN: str = Field(default=os.getenv("API_AUTH_TOKEN", ""), description="API authentication token - REQUIRED")
11
  API_TIMEOUT: int = Field(default=int(os.getenv("API_TIMEOUT", "120")), description="API request timeout in seconds")
12
 
13
  def model_post_init(self, __context) -> None:
14
 
15
  if not self.API_AUTH_TOKEN:
16
+ raise ValueError("❌ API_AUTH_TOKEN environment variable must be set")
17
 
18
+ # Simplified forbidden token check
19
  forbidden_tokens = {
20
  "",
21
  "your_secret_token_here",
 
23
  }
24
 
25
  if self.API_AUTH_TOKEN in forbidden_tokens:
26
+ raise ValueError(f"❌ Forbidden placeholder token: {self.API_AUTH_TOKEN}")
27
 
28
+ # Simplified: only require basic length
29
  if len(self.API_AUTH_TOKEN) < 8:
30
+ raise ValueError("❌ Authentication token must be at least 8 characters long")
31
 
32
  # Polling Configuration
33
  POLL_INTERVAL: float = Field(default=3.0, description="Status polling interval in seconds")
 
58
  }
59
 
60
  def get_api_url(endpoint: str) -> str:
61
+ """Construct full API URL from endpoint"""
62
+ base_url = str(CONFIG.API_BASE_URL).rstrip('/')
63
+ endpoint_clean = endpoint.lstrip('/')
64
+ return f"{base_url}/{endpoint_clean}"
examples/character_figure_input_example1.jpeg ADDED

Git LFS Details

  • SHA256: b8bbba1a47bc74a655ef1d900829aa9a23a5dc2d59c207de0670b5f1b534edd6
  • Pointer size: 130 Bytes
  • Size of remote file: 99.5 kB
examples/character_figure_input_example2.jpeg ADDED

Git LFS Details

  • SHA256: 3f62129cb9d3c04b96ef21cb929a4bab17c4fd36532fcd62ff747d36efcf826d
  • Pointer size: 131 Bytes
  • Size of remote file: 309 kB
examples/character_figure_input_example3.jpeg ADDED

Git LFS Details

  • SHA256: 13e226cda5412fb72c8a6cd07a5c336956da87da15f59adfb25bc54722a910c1
  • Pointer size: 132 Bytes
  • Size of remote file: 1.63 MB
examples/character_figure_input_example4.jpeg ADDED

Git LFS Details

  • SHA256: 1a76b38dd674b9da5d00d59722e7f8ea8e61a57a37904f5bbdc05a1e2760ea2e
  • Pointer size: 131 Bytes
  • Size of remote file: 108 kB
examples/figure_3d_input_example.jpeg ADDED

Git LFS Details

  • SHA256: 07343938b3a1b646a6b7db68aacf1e426c3a5486a16e12dcbacafae9bdb40a13
  • Pointer size: 131 Bytes
  • Size of remote file: 175 kB
examples/figure_3d_input_example_old.jpeg ADDED

Git LFS Details

  • SHA256: ac4cb65c52bd9cb575d4b77cc63a8b8a5bd84cad2986046648494780b36c0345
  • Pointer size: 132 Bytes
  • Size of remote file: 1.34 MB
examples/results/character_figure_collaboration_example1.jpg ADDED

Git LFS Details

  • SHA256: 7134725c786411b182816d2e7af2c8175c4d6d1490c6c2d1046fdb6e7656e562
  • Pointer size: 131 Bytes
  • Size of remote file: 901 kB
examples/results/character_figure_collaboration_example2.jpg ADDED

Git LFS Details

  • SHA256: e729fca2c7beaf7242ffdcfa7f4f7afaa21f66658940ec09dccbc043ec664567
  • Pointer size: 132 Bytes
  • Size of remote file: 1.14 MB
examples/results/character_figure_collaboration_example3.jpg ADDED

Git LFS Details

  • SHA256: 698e7cd5249847a624f7665ba6c9fc65a063164ac3bd942a8543df9578e0df1d
  • Pointer size: 131 Bytes
  • Size of remote file: 982 kB
examples/results/character_figure_collaboration_example4.jpg ADDED

Git LFS Details

  • SHA256: b18c8e4cafd64cea4d8596a2c087644d26607096d9a0eaae2dd8ca92a68ba06f
  • Pointer size: 131 Bytes
  • Size of remote file: 641 kB
examples/results/figure_3d_alice_tea_party.png ADDED

Git LFS Details

  • SHA256: 02758e6f0b48ae284a0fe96b82b79c9098a4662395c087df00ed25b1ea10a2a2
  • Pointer size: 132 Bytes
  • Size of remote file: 1.53 MB
examples/results/figure_3d_collector_shelf.png ADDED

Git LFS Details

  • SHA256: 11fd1ed73138d84e7cc7c70f004170076476be12bc80cdfa7b3228ff00ebc7e8
  • Pointer size: 132 Bytes
  • Size of remote file: 4.22 MB
examples/results/figure_3d_desktop_display.png ADDED

Git LFS Details

  • SHA256: eade0e3ff5e63392df9b19c885e6cd59a0246c024501520fa9f07d3c99b6ef11
  • Pointer size: 132 Bytes
  • Size of remote file: 4.68 MB
examples/results/figure_3d_miniature_adventure.png ADDED

Git LFS Details

  • SHA256: 92a1f09f0c90e587c58bc6ded8568a66eeaad92de3c4aeb1c524abf8be0e0b2b
  • Pointer size: 132 Bytes
  • Size of remote file: 1.98 MB
examples/results/figure_3d_professional_lighting.png ADDED

Git LFS Details

  • SHA256: 0ffd476e98ea521255271a3841a068ef03172dc2bf512b2976283aa0bf206e9f
  • Pointer size: 132 Bytes
  • Size of remote file: 4.21 MB
examples_config.py CHANGED
@@ -468,6 +468,63 @@ def get_random_five_view_examples(count=1):
468
 
469
  return random.sample(FIVE_VIEW_GENERATION_EXAMPLES_WITH_RESULTS, count)
470
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
471
  # 图像扩展示例配置 - 使用多样化图片和真实AI生成的扩图结果
472
  IMAGE_OUTPAINTING_EXAMPLES_WITH_RESULTS = [
473
 
 
468
 
469
  return random.sample(FIVE_VIEW_GENERATION_EXAMPLES_WITH_RESULTS, count)
470
 
471
+ # 3D手办生成示例配置
472
+ FIGURE_3D_EXAMPLES_WITH_RESULTS = [
473
+ # 格式: [输入图片路径, 手办风格, 结果图片路径]
474
+ [
475
+ "examples/figure_3d_input_example.jpeg",
476
+ "professional_lighting",
477
+ "examples/results/figure_3d_professional_lighting.png"
478
+ ],
479
+ [
480
+ "examples/figure_3d_input_example.jpeg",
481
+ "collector_shelf",
482
+ "examples/results/figure_3d_collector_shelf.png"
483
+ ],
484
+ [
485
+ "examples/figure_3d_input_example.jpeg",
486
+ "desktop_display",
487
+ "examples/results/figure_3d_desktop_display.png"
488
+ ]
489
+ ]
490
+
491
+ def get_random_figure_3d_examples(count=3):
492
+ import random
493
+
494
+ if len(FIGURE_3D_EXAMPLES_WITH_RESULTS) <= count:
495
+ return FIGURE_3D_EXAMPLES_WITH_RESULTS.copy()
496
+
497
+ return random.sample(FIGURE_3D_EXAMPLES_WITH_RESULTS, count)
498
+
499
+ # 人物手办合影示例配置
500
+ CHARACTER_FIGURE_COLLABORATION_EXAMPLES_WITH_RESULTS = [
501
+ # 格式: [输入图片路径, 结果图片路径]
502
+ [
503
+ "examples/character_figure_input_example1.jpeg",
504
+ "examples/results/character_figure_collaboration_example1.jpg"
505
+ ],
506
+ [
507
+ "examples/character_figure_input_example2.jpeg",
508
+ "examples/results/character_figure_collaboration_example2.jpg"
509
+ ],
510
+ [
511
+ "examples/character_figure_input_example3.jpeg",
512
+ "examples/results/character_figure_collaboration_example3.jpg"
513
+ ],
514
+ [
515
+ "examples/character_figure_input_example4.jpeg",
516
+ "examples/results/character_figure_collaboration_example4.jpg"
517
+ ]
518
+ ]
519
+
520
+ def get_random_character_figure_collaboration_examples(count=1):
521
+ import random
522
+
523
+ if len(CHARACTER_FIGURE_COLLABORATION_EXAMPLES_WITH_RESULTS) <= count:
524
+ return CHARACTER_FIGURE_COLLABORATION_EXAMPLES_WITH_RESULTS.copy()
525
+
526
+ return random.sample(CHARACTER_FIGURE_COLLABORATION_EXAMPLES_WITH_RESULTS, count)
527
+
528
  # 图像扩展示例配置 - 使用多样化图片和真实AI生成的扩图结果
529
  IMAGE_OUTPAINTING_EXAMPLES_WITH_RESULTS = [
530
 
generate_examples.py ADDED
@@ -0,0 +1,215 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+ """
3
+ 自动化脚本:为3D手办功能生成新的示例结果图片
4
+ """
5
+
6
+ import asyncio
7
+ import base64
8
+ import httpx
9
+ import json
10
+ import os
11
+ import time
12
+ from PIL import Image
13
+ from io import BytesIO
14
+
15
+ # 配置
16
+ API_BASE_URL = "https://1251722089-fabl8rzpod.ap-singapore.tencentscf.com"
17
+ INPUT_IMAGE_PATH = "examples/figure_3d_input_example.jpeg"
18
+ OUTPUT_DIR = "examples/results"
19
+ TIMEOUT_SECONDS = 600 # 10分钟
20
+
21
+ # 认证配置
22
+ AUTH_HEADERS = {
23
+ "Content-Type": "application/json",
24
+ "X-API-Auth": "local_development_token_12345678"
25
+ }
26
+
27
+ # 3D手办风格配置
28
+ STYLES = [
29
+ ("professional_lighting", "💡 专业灯光场景"),
30
+ ("collector_shelf", "📚 爱好者收藏架场景"),
31
+ ("desktop_display", "💻 电脑桌展示")
32
+ ]
33
+
34
+ def load_and_encode_image(image_path):
35
+ """加载图片并转换为Base64编码"""
36
+ try:
37
+ with open(image_path, 'rb') as f:
38
+ image_data = f.read()
39
+
40
+ # 转换为Base64
41
+ base64_data = base64.b64encode(image_data).decode('utf-8')
42
+ print(f"✅ 成功加载图片: {image_path}")
43
+ print(f"📏 图片大小: {len(image_data)} bytes")
44
+ return base64_data
45
+ except Exception as e:
46
+ print(f"❌ 加载图片失败: {e}")
47
+ return None
48
+
49
+ async def submit_task(style_key, image_data):
50
+ """提交3D手办生成任务"""
51
+ url = f"{API_BASE_URL}/api/v1/tasks/figure-3d-generation"
52
+
53
+ payload = {
54
+ "image_data": image_data,
55
+ "figure_style": style_key,
56
+ "resolution": "square - 1024x1024 (1:1)"
57
+ }
58
+
59
+ headers = AUTH_HEADERS
60
+
61
+ try:
62
+ async with httpx.AsyncClient(timeout=30.0) as client:
63
+ print(f"🚀 提交任务: {style_key}")
64
+ response = await client.post(url, json=payload, headers=headers)
65
+
66
+ if response.status_code in [200, 201]:
67
+ result = response.json()
68
+ task_id = result.get("task_id")
69
+ print(f"✅ 任务提交成功: {task_id}")
70
+ return task_id
71
+ else:
72
+ print(f"❌ 任务提交失败: {response.status_code} - {response.text}")
73
+ return None
74
+
75
+ except Exception as e:
76
+ print(f"❌ 提交任务异常: {e}")
77
+ return None
78
+
79
+ async def wait_for_completion(task_id):
80
+ """等待任务完成"""
81
+ url = f"{API_BASE_URL}/api/v1/tasks/{task_id}"
82
+
83
+ start_time = time.time()
84
+
85
+ async with httpx.AsyncClient(timeout=30.0) as client:
86
+ while True:
87
+ try:
88
+ response = await client.get(url, headers=AUTH_HEADERS)
89
+
90
+ if response.status_code == 200:
91
+ result = response.json()
92
+ status = result.get("status")
93
+
94
+ print(f"📊 任务状态: {status}")
95
+
96
+ if status == "completed":
97
+ print("✅ 任务完成!")
98
+ return result
99
+ elif status == "failed":
100
+ print(f"❌ 任务失败: {result.get('error', 'Unknown error')}")
101
+ return None
102
+ elif status in ["pending", "running", "processing"]:
103
+ # 检查超时
104
+ elapsed = time.time() - start_time
105
+ if elapsed > TIMEOUT_SECONDS:
106
+ print(f"⏰ 任务超时 ({TIMEOUT_SECONDS}秒)")
107
+ return None
108
+
109
+ print(f"⏳ 等待中... (已等待 {elapsed:.0f}秒)")
110
+ await asyncio.sleep(10) # 每10秒检查一次
111
+ else:
112
+ print(f"❓ 未知状态: {status}")
113
+ await asyncio.sleep(5)
114
+
115
+ else:
116
+ print(f"❌ 状态查询失败: {response.status_code}")
117
+ await asyncio.sleep(5)
118
+
119
+ except Exception as e:
120
+ print(f"❌ 状态查询异常: {e}")
121
+ await asyncio.sleep(5)
122
+
123
+ async def download_result(task_id, style_key):
124
+ """下载任务结果"""
125
+ url = f"{API_BASE_URL}/api/v1/tasks/{task_id}/result"
126
+
127
+ try:
128
+ async with httpx.AsyncClient(timeout=60.0) as client:
129
+ response = await client.get(url, headers=AUTH_HEADERS)
130
+
131
+ if response.status_code == 200:
132
+ result = response.json()
133
+ result_url = result.get("result_url")
134
+
135
+ if result_url:
136
+ # 下载图片 (图片URL通常不需要认证,但为了保险起见也加上)
137
+ img_response = await client.get(result_url)
138
+ if img_response.status_code == 200:
139
+ # 保存图片
140
+ output_path = os.path.join(OUTPUT_DIR, f"figure_3d_{style_key}.png")
141
+
142
+ # 确保输出目录存在
143
+ os.makedirs(OUTPUT_DIR, exist_ok=True)
144
+
145
+ with open(output_path, 'wb') as f:
146
+ f.write(img_response.content)
147
+
148
+ print(f"✅ 结果已保存: {output_path}")
149
+ return output_path
150
+ else:
151
+ print(f"❌ 下载图片失败: {img_response.status_code}")
152
+ else:
153
+ print("❌ 结果中没有图片URL")
154
+ else:
155
+ print(f"❌ 获取结果失败: {response.status_code}")
156
+
157
+ except Exception as e:
158
+ print(f"❌ 下载结果异常: {e}")
159
+
160
+ return None
161
+
162
+ async def generate_example(style_key, style_name, image_data):
163
+ """生成单个示例"""
164
+ print(f"\n{'='*50}")
165
+ print(f"🎨 开始生成示例: {style_name}")
166
+ print(f"{'='*50}")
167
+
168
+ # 1. 提交任务
169
+ task_id = await submit_task(style_key, image_data)
170
+ if not task_id:
171
+ return False
172
+
173
+ # 2. 等待完成
174
+ result = await wait_for_completion(task_id)
175
+ if not result:
176
+ return False
177
+
178
+ # 3. 下载结果
179
+ output_path = await download_result(task_id, style_key)
180
+ if output_path:
181
+ print(f"🎉 示例生成成功: {style_name}")
182
+ return True
183
+ else:
184
+ print(f"💥 示例生成失败: {style_name}")
185
+ return False
186
+
187
+ async def main():
188
+ """主函数"""
189
+ print("🚀 开始生成3D手办示例...")
190
+
191
+ # 加载输入图片
192
+ image_data = load_and_encode_image(INPUT_IMAGE_PATH)
193
+ if not image_data:
194
+ print("❌ 无法加载输入图片,退出")
195
+ return
196
+
197
+ success_count = 0
198
+
199
+ # 逐个生成示例(每次只运行一个)
200
+ for style_key, style_name in STYLES:
201
+ success = await generate_example(style_key, style_name, image_data)
202
+ if success:
203
+ success_count += 1
204
+
205
+ # 在任务之间稍作休息
206
+ if style_key != STYLES[-1][0]: # 不是最后一个
207
+ print("\n⏸️ 休息5秒后继续下一个...")
208
+ await asyncio.sleep(5)
209
+
210
+ print(f"\n{'='*50}")
211
+ print(f"🏁 生成完成! 成功: {success_count}/{len(STYLES)}")
212
+ print(f"{'='*50}")
213
+
214
+ if __name__ == "__main__":
215
+ asyncio.run(main())
models.py CHANGED
@@ -31,6 +31,7 @@ class TaskInfo(BaseModel):
31
  error_message: Optional[str] = Field(default=None, description="Error message if failed")
32
 
33
  @validator('task_id')
 
34
  def validate_task_id(cls, v):
35
 
36
  try:
@@ -169,8 +170,11 @@ class AppSession(BaseModel):
169
  return self.active_task_id == task_id
170
 
171
  def can_submit_new_task(self) -> bool:
172
-
173
- return self.current_ui_state.state in {UIState.IDLE, UIState.COMPLETED, UIState.ERROR}
 
 
 
174
 
175
  class Config:
176
  frozen = False # Mutable for state updates
@@ -194,7 +198,38 @@ class FiveViewGenerationSubmission(BaseModel):
194
  image_data: str = Field(..., description="Base64编码的图片数据")
195
 
196
  def to_api_payload(self) -> dict:
197
-
198
  return {
199
  "image_data": self.image_data
200
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
31
  error_message: Optional[str] = Field(default=None, description="Error message if failed")
32
 
33
  @validator('task_id')
34
+ @classmethod
35
  def validate_task_id(cls, v):
36
 
37
  try:
 
170
  return self.active_task_id == task_id
171
 
172
  def can_submit_new_task(self) -> bool:
173
+ """Check if a new task can be submitted based on current UI state"""
174
+ current_state = self.current_ui_state
175
+ if hasattr(current_state, 'state'):
176
+ return current_state.state in {UIState.IDLE, UIState.COMPLETED, UIState.ERROR}
177
+ return True # Default to allowing submission if state is unclear
178
 
179
  class Config:
180
  frozen = False # Mutable for state updates
 
198
  image_data: str = Field(..., description="Base64编码的图片数据")
199
 
200
  def to_api_payload(self) -> dict:
201
+
202
  return {
203
  "image_data": self.image_data
204
  }
205
+
206
+
207
+ class Figure3DSubmission(BaseModel):
208
+
209
+ image_data: str = Field(..., description="Base64编码的2D角色图片数据")
210
+ figure_style: str = Field(..., description="3D手办风格")
211
+ resolution: str = Field(
212
+ default="square - 1024x1024 (1:1)",
213
+ description="图像分辨率"
214
+ )
215
+
216
+ def to_api_payload(self) -> dict:
217
+
218
+ return {
219
+ "image_data": self.image_data,
220
+ "figure_style": self.figure_style,
221
+ "resolution": self.resolution
222
+ }
223
+
224
+
225
+ class CharacterFigureCollaborationSubmission(BaseModel):
226
+
227
+ image_data: str = Field(..., description="Base64编码的人物全身照片数据")
228
+
229
+ def to_api_payload(self) -> dict[str, Any]:
230
+
231
+ return {
232
+ "image_data": self.image_data
233
+ }
234
+
235
+