Compare commits
6 Commits
ea7522a45d
...
develop
| Author | SHA1 | Date | |
|---|---|---|---|
| 893f5e87b4 | |||
| c73bfa7e2a | |||
| ad4db736de | |||
| cfbd9e47ac | |||
| 6892361050 | |||
| f0b73d5fc1 |
@@ -11,6 +11,7 @@ from app.api import api_precompute
|
|||||||
from app.api import api_prompt_generation
|
from app.api import api_prompt_generation
|
||||||
from app.api import api_recommendation
|
from app.api import api_recommendation
|
||||||
from app.api import api_test
|
from app.api import api_test
|
||||||
|
from app.api import api_sketch_to_garment
|
||||||
|
|
||||||
router = APIRouter()
|
router = APIRouter()
|
||||||
|
|
||||||
@@ -26,6 +27,7 @@ router.include_router(api_precompute.router, tags=['api_precompute'], prefix="/a
|
|||||||
router.include_router(api_mannequins_edit.router, tags=['api_mannequins_edit'], prefix="/api")
|
router.include_router(api_mannequins_edit.router, tags=['api_mannequins_edit'], prefix="/api")
|
||||||
router.include_router(api_pose_transform.router, tags=['api_pose_transform'], prefix="/api")
|
router.include_router(api_pose_transform.router, tags=['api_pose_transform'], prefix="/api")
|
||||||
router.include_router(api_clothing_seg.router, tags=['api_clothing_seg'], prefix="/api")
|
router.include_router(api_clothing_seg.router, tags=['api_clothing_seg'], prefix="/api")
|
||||||
|
router.include_router(api_sketch_to_garment.router, tags=['sketch_to_garment'], prefix="/api")
|
||||||
|
|
||||||
"""停用"""
|
"""停用"""
|
||||||
# from app.api import api_chat_robot
|
# from app.api import api_chat_robot
|
||||||
|
|||||||
104
app/api/api_sketch_to_garment.py
Normal file
104
app/api/api_sketch_to_garment.py
Normal file
@@ -0,0 +1,104 @@
|
|||||||
|
import json
|
||||||
|
import logging
|
||||||
|
|
||||||
|
from fastapi import APIRouter, HTTPException
|
||||||
|
|
||||||
|
from app.schemas.response_template import ResponseModel
|
||||||
|
from app.schemas.sketch_to_garment_schemas import SketchToGarmentModel
|
||||||
|
from app.service.sketch2garment.server import submit_sketch_to_garment_task
|
||||||
|
|
||||||
|
logger = logging.getLogger()
|
||||||
|
router = APIRouter()
|
||||||
|
|
||||||
|
|
||||||
|
@router.post("/sketch_to_garment")
|
||||||
|
def sketch_to_garment_api(request_item: SketchToGarmentModel):
|
||||||
|
"""
|
||||||
|
### 接口说明:
|
||||||
|
将图片转换为3D模型(异步处理)。接口接收请求后立即返回任务ID,后台通过 Celery 处理,处理完成后结果会通过 RabbitMQ 发送。
|
||||||
|
|
||||||
|
### 参数说明:
|
||||||
|
- **input_image_path**: 输入图片路径
|
||||||
|
- **bucket_name**: bucket name
|
||||||
|
- **user_id**: 用户id
|
||||||
|
- **callback_url**: 回调url
|
||||||
|
- **task_id**: 任务id
|
||||||
|
- **model**: 转换模式 文本和图片 ,默认只有图片
|
||||||
|
|
||||||
|
### 请求体示例:
|
||||||
|
**单张图片模式:**
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"input_image_path": "test/53d38bd5-f77b-4034-ada2-45f1e2ebe00c.png",
|
||||||
|
"bucket_name": "test",
|
||||||
|
"user_id": "string-456",
|
||||||
|
"callback_url": "http://18.167.251.121:10015/api/image/webhook/img-to-3d",
|
||||||
|
"task_id": "string12",
|
||||||
|
"model": "picture"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
### 输出示例:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"code": 200,
|
||||||
|
"msg": "OK!",
|
||||||
|
"data": {
|
||||||
|
"state": "success",
|
||||||
|
"task_id": "string12",
|
||||||
|
"message": "任务已成功提交,正在后台处理..."
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
### 错误输出
|
||||||
|
参考文档: https://platform.tripo3d.ai/docs/error-handling
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"code": 500,
|
||||||
|
"message": "You don’t have enough credit to create this task",
|
||||||
|
"data": {
|
||||||
|
"status": "fail",
|
||||||
|
"task_id": "123",
|
||||||
|
"message": "You don’t have enough credit to create this task",
|
||||||
|
"error": str(e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
回调请求参数例子:
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"task_id": "string12",
|
||||||
|
"status": "success",
|
||||||
|
"result": {
|
||||||
|
"pattern": "test/string-456/pattern_making/now_string-456_pattern.png",
|
||||||
|
"texture": "test/string-456/pattern_making/now_string-456_texture.png",
|
||||||
|
"glb": "test/string-456/pattern_making/now_string-456_sim.glb",
|
||||||
|
"texture_fabric": "test/string-456/pattern_making/now_string-456_texture_fabric.png"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
logger.info(f"sketch_to_garment request item is : @@@@@@:{json.dumps(request_item.model_dump(), indent=4)}")
|
||||||
|
result = submit_sketch_to_garment_task(
|
||||||
|
task_id=request_item.task_id,
|
||||||
|
callback_url=request_item.callback_url,
|
||||||
|
bucket_name=request_item.bucket_name,
|
||||||
|
input_image_path=request_item.input_image_path,
|
||||||
|
user_id=request_item.user_id,
|
||||||
|
model=request_item.model
|
||||||
|
)
|
||||||
|
result = {
|
||||||
|
"state": "success",
|
||||||
|
"task_id": request_item.task_id,
|
||||||
|
"message": "任务已成功提交,正在后台处理...",
|
||||||
|
}
|
||||||
|
state_code = 200
|
||||||
|
return ResponseModel(data=result, code=state_code)
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logger.warning(f"super_resolution Run Exception @@@@@@:{e}")
|
||||||
|
raise HTTPException(status_code=404, detail=str(e))
|
||||||
@@ -1,5 +1,21 @@
|
|||||||
|
import logging
|
||||||
|
from typing import Dict, Any
|
||||||
|
|
||||||
|
import yaml
|
||||||
from pydantic import Field
|
from pydantic import Field
|
||||||
from pydantic_settings import BaseSettings, SettingsConfigDict
|
from pydantic_settings import BaseSettings, SettingsConfigDict
|
||||||
|
from v2.nacos import ClientConfigBuilder, GRPCConfig, NacosConfigService, ConfigParam, NacosNamingService, RegisterInstanceParam, DeregisterInstanceParam
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
# ====================== Nacos 配置 ======================
|
||||||
|
NACOS_SERVER_ADDRESSES = "18.167.251.121:28848"
|
||||||
|
NACOS_NAMESPACE = "zcr"
|
||||||
|
NACOS_USERNAME = "nacos"
|
||||||
|
NACOS_PASSWORD = "Aidlab123123!"
|
||||||
|
NACOS_GROUP = "LOCAL"
|
||||||
|
NACOS_DATA_ID = "aida.python"
|
||||||
|
SERVICE_NAME = "fastapi-service" # ←←← 必须修改!建议格式:项目名-环境,例如 ai-image-service-dev
|
||||||
|
|
||||||
|
|
||||||
class Settings(BaseSettings):
|
class Settings(BaseSettings):
|
||||||
@@ -71,6 +87,9 @@ class Settings(BaseSettings):
|
|||||||
A6000_SERVICE_HOST: str = Field(default='', description="")
|
A6000_SERVICE_HOST: str = Field(default='', description="")
|
||||||
B_4_X_4090_SERVICE_HOST: str = Field(default='', description="")
|
B_4_X_4090_SERVICE_HOST: str = Field(default='', description="")
|
||||||
|
|
||||||
|
# --- sketch to garment 模型url ---
|
||||||
|
SKETCH_TO_GARMENT_URL: str = Field(default='', description="")
|
||||||
|
|
||||||
# --- 其他配置信息 以下均为Docker容器内配置---
|
# --- 其他配置信息 以下均为Docker容器内配置---
|
||||||
LOGS_PATH: str = Field(default="/logs/", description="")
|
LOGS_PATH: str = Field(default="/logs/", description="")
|
||||||
CATEGORY_PATH: str = Field(default="/app/service/attribute/config/descriptor/category/category_dis.csv", description="")
|
CATEGORY_PATH: str = Field(default="/app/service/attribute/config/descriptor/category/category_dis.csv", description="")
|
||||||
|
|||||||
22
app/main.py
22
app/main.py
@@ -1,5 +1,8 @@
|
|||||||
# 1. 这里的顺序至关重要!必须在最顶端
|
# 1. 这里的顺序至关重要!必须在最顶端
|
||||||
import sys
|
import sys
|
||||||
|
from contextlib import asynccontextmanager
|
||||||
|
|
||||||
|
# from app.core.nacos_config import load_nacos_config, register_server, deregister_server
|
||||||
|
|
||||||
try:
|
try:
|
||||||
import asyncore
|
import asyncore
|
||||||
@@ -30,8 +33,21 @@ logger = logging.getLogger(__name__)
|
|||||||
load_dotenv()
|
load_dotenv()
|
||||||
|
|
||||||
|
|
||||||
|
# @asynccontextmanager
|
||||||
|
# async def lifespan(app: FastAPI):
|
||||||
|
# try:
|
||||||
|
# load_nacos_config()
|
||||||
|
# register_server()
|
||||||
|
#
|
||||||
|
# yield
|
||||||
|
# finally:
|
||||||
|
# deregister_server()
|
||||||
|
# logger.info("lifespan down")
|
||||||
|
|
||||||
|
|
||||||
def get_application() -> FastAPI:
|
def get_application() -> FastAPI:
|
||||||
application = FastAPI(
|
application = FastAPI(
|
||||||
|
# lifespan=lifespan,
|
||||||
docs_url="/docs",
|
docs_url="/docs",
|
||||||
redoc_url='/re-docs',
|
redoc_url='/re-docs',
|
||||||
openapi_url=f"/openapi.json",
|
openapi_url=f"/openapi.json",
|
||||||
@@ -64,5 +80,11 @@ async def http_exception_handler(exc: HTTPException):
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@app.get("/health", operation_id="health")
|
||||||
|
async def health():
|
||||||
|
logger.info("health check")
|
||||||
|
return {"ok": True, "env": settings.APP_ENV}
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
uvicorn.run(app, host="0.0.0.0", port=settings.PORT)
|
uvicorn.run(app, host="0.0.0.0", port=settings.PORT)
|
||||||
|
|||||||
12
app/schemas/sketch_to_garment_schemas.py
Normal file
12
app/schemas/sketch_to_garment_schemas.py
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
from typing import List
|
||||||
|
|
||||||
|
from pydantic import BaseModel, Field
|
||||||
|
|
||||||
|
|
||||||
|
class SketchToGarmentModel(BaseModel):
|
||||||
|
input_image_path: str = Field(..., description="输入图片路径列表")
|
||||||
|
bucket_name: str = Field(..., description="输入图片路径列表")
|
||||||
|
user_id: str = Field(..., description="用户id")
|
||||||
|
callback_url: str # 必填,客户端提供的回调地址
|
||||||
|
task_id: str = Field()
|
||||||
|
model: str = Field(default="single", description="模型类型: single 或 multi")
|
||||||
@@ -27,7 +27,7 @@ class NoSegPrintPainting:
|
|||||||
# 获取平铺 + 旋转 的overall print
|
# 获取平铺 + 旋转 的overall print
|
||||||
painting_dict = self.painting_collection(painting_dict, overall_print)
|
painting_dict = self.painting_collection(painting_dict, overall_print)
|
||||||
result['no_seg_sketch_overall'] = result['no_seg_sketch_print'] = self.printpaint(result, painting_dict, print_=True)
|
result['no_seg_sketch_overall'] = result['no_seg_sketch_print'] = self.printpaint(result, painting_dict, print_=True)
|
||||||
result['pattern_image'] = result['no_seg_sketch_overall']
|
# result['pattern_image'] = result['no_seg_sketch_overall']
|
||||||
|
|
||||||
if single_print:
|
if single_print:
|
||||||
print_background = np.zeros((result['pattern_image'].shape[0], result['pattern_image'].shape[1], 3), dtype=np.uint8)
|
print_background = np.zeros((result['pattern_image'].shape[0], result['pattern_image'].shape[1], 3), dtype=np.uint8)
|
||||||
@@ -166,15 +166,17 @@ class NoSegPrintPainting:
|
|||||||
dim_max = max(painting_dict['dim_image_h'], painting_dict['dim_image_w'])
|
dim_max = max(painting_dict['dim_image_h'], painting_dict['dim_image_w'])
|
||||||
dim_pattern = (int(dim_max * print_['scale'] / 5), int(dim_max * print_['scale'] / 5))
|
dim_pattern = (int(dim_max * print_['scale'] / 5), int(dim_max * print_['scale'] / 5))
|
||||||
gap = print_dict.get('gap', [[0, 0]])[0]
|
gap = print_dict.get('gap', [[0, 0]])[0]
|
||||||
painting_dict['tile_print'] = tile_image(pattern=print_['image'],
|
painting_dict['tile_print'], painting_dict['mask_inv_print'] = tile_image(pattern=print_['image'],
|
||||||
dim=dim_pattern,
|
mask=print_['mask'],
|
||||||
gap_x=gap[0],
|
dim=dim_pattern,
|
||||||
gap_y=gap[1],
|
gap_x=gap[0],
|
||||||
canvas_h=painting_dict['dim_image_h'],
|
gap_y=gap[1],
|
||||||
canvas_w=painting_dict['dim_image_w'],
|
canvas_h=painting_dict['dim_image_h'],
|
||||||
location=painting_dict['location'],
|
canvas_w=painting_dict['dim_image_w'],
|
||||||
angle=int(print_.get('print_angle_list', [0])[0]))
|
location=painting_dict['location'],
|
||||||
painting_dict['mask_inv_print'] = np.zeros(painting_dict['tile_print'].shape[:2], dtype=np.uint8)
|
angle=int(print_.get('print_angle_list', [0])[0]))
|
||||||
|
# painting_dict['mask_inv_print'] = np.zeros(painting_dict['tile_print'].shape[:2], dtype=np.uint8)
|
||||||
|
# painting_dict['mask_inv_print'] = self.get_mask_inv(painting_dict['tile_print'])
|
||||||
return painting_dict
|
return painting_dict
|
||||||
|
|
||||||
def tile_image(self, pattern, dim, scale, dim_image_h, dim_image_w, location, trigger=False):
|
def tile_image(self, pattern, dim, scale, dim_image_h, dim_image_w, location, trigger=False):
|
||||||
@@ -255,10 +257,15 @@ class NoSegPrintPainting:
|
|||||||
image = oss_get_image(oss_client=self.minio_client, bucket=bucket_name, object_name=object_name, data_type="PIL")
|
image = oss_get_image(oss_client=self.minio_client, bucket=bucket_name, object_name=object_name, data_type="PIL")
|
||||||
# 判断图片格式,如果是RGBA 则贴在一张纯白图片上 防止透明转黑
|
# 判断图片格式,如果是RGBA 则贴在一张纯白图片上 防止透明转黑
|
||||||
if image.mode == "RGBA":
|
if image.mode == "RGBA":
|
||||||
|
mask_pil = image.split()[3]
|
||||||
new_background = Image.new('RGB', image.size, (255, 255, 255))
|
new_background = Image.new('RGB', image.size, (255, 255, 255))
|
||||||
new_background.paste(image, mask=image.split()[3])
|
new_background.paste(image, mask=image.split()[3])
|
||||||
image = new_background
|
image = new_background
|
||||||
|
else:
|
||||||
|
mask_pil = Image.new('L', image.size, 255) # L=灰度图,255=纯白
|
||||||
print_dict['image'] = cv2.cvtColor(np.asarray(image), cv2.COLOR_RGB2BGR)
|
print_dict['image'] = cv2.cvtColor(np.asarray(image), cv2.COLOR_RGB2BGR)
|
||||||
|
print_dict['mask'] = cv2.threshold(np.array(mask_pil), 127, 255, cv2.THRESH_BINARY)[1]
|
||||||
|
|
||||||
return print_dict
|
return print_dict
|
||||||
|
|
||||||
def crop_image(self, image, image_size_h, image_size_w, location, print_shape):
|
def crop_image(self, image, image_size_h, image_size_w, location, print_shape):
|
||||||
@@ -408,9 +415,12 @@ class NoSegPrintPainting:
|
|||||||
return cropped_img
|
return cropped_img
|
||||||
|
|
||||||
|
|
||||||
def tile_image(pattern, dim, gap_x, gap_y, canvas_h, canvas_w, location, angle=0):
|
def tile_image(pattern, mask, dim, gap_x, gap_y, canvas_h, canvas_w, location, angle=0):
|
||||||
"""
|
"""
|
||||||
按照指定的 X/Y 间距平铺印花,并支持旋转
|
按照指定的 X/Y 间距平铺印花,并支持旋转
|
||||||
|
【修改版】以被平铺图案的【中心】作为平铺基准点
|
||||||
|
|
||||||
|
:param location: [[center_y, center_x]] → 第一个图案中心的坐标
|
||||||
:param angle: 旋转角度 (度数, 逆时针)
|
:param angle: 旋转角度 (度数, 逆时针)
|
||||||
"""
|
"""
|
||||||
# 1. 确保输入是 RGBA
|
# 1. 确保输入是 RGBA
|
||||||
@@ -422,35 +432,54 @@ def tile_image(pattern, dim, gap_x, gap_y, canvas_h, canvas_w, location, angle=0
|
|||||||
rotated_p = rotate_image(resized_p, angle)
|
rotated_p = rotate_image(resized_p, angle)
|
||||||
p_h, p_w = rotated_p.shape[:2]
|
p_h, p_w = rotated_p.shape[:2]
|
||||||
|
|
||||||
# 3. 创建透明单元格
|
# 3. 创建透明单元格(图案放在单元格中心)
|
||||||
cell_h, cell_w = p_h + gap_y, p_w + gap_x
|
cell_h = p_h + gap_y
|
||||||
|
cell_w = p_w + gap_x
|
||||||
|
|
||||||
unit_cell = np.zeros((cell_h, cell_w, 4), dtype=np.uint8)
|
unit_cell = np.zeros((cell_h, cell_w, 4), dtype=np.uint8)
|
||||||
unit_cell[:p_h, :p_w, :] = rotated_p
|
|
||||||
|
# 计算图案在单元格中的左上角位置(让图案居中)
|
||||||
|
start_y = (cell_h - p_h) // 2
|
||||||
|
start_x = (cell_w - p_w) // 2
|
||||||
|
unit_cell[start_y:start_y + p_h, start_x:start_x + p_w, :] = rotated_p
|
||||||
|
|
||||||
# 4. 执行平铺
|
# 4. 执行平铺
|
||||||
tiles_y = (canvas_h // cell_h) + 2
|
tiles_y = (canvas_h // cell_h) + 3 # 多加一点余量更安全
|
||||||
tiles_x = (canvas_w // cell_w) + 2
|
tiles_x = (canvas_w // cell_w) + 3
|
||||||
full_tiled = np.tile(unit_cell, (tiles_y, tiles_x, 1))
|
full_tiled = np.tile(unit_cell, (tiles_y, tiles_x, 1))
|
||||||
|
|
||||||
# 5. 裁剪平铺层
|
# 5. 计算偏移(关键修改:以中心为基准)
|
||||||
offset_x = int(location[0][1] % cell_w)
|
center_y, center_x = location[0][0], location[0][1] # 第一个图案的中心位置
|
||||||
offset_y = int(location[0][0] % cell_h)
|
|
||||||
|
# 计算从哪个位置开始裁剪,才能让中心落在指定坐标
|
||||||
|
offset_y = int((center_y - (p_h // 2)) % cell_h)
|
||||||
|
offset_x = int((center_x - (p_w // 2)) % cell_w)
|
||||||
|
|
||||||
tiled_layer = full_tiled[offset_y: offset_y + canvas_h,
|
tiled_layer = full_tiled[offset_y: offset_y + canvas_h,
|
||||||
offset_x: offset_x + canvas_w]
|
offset_x: offset_x + canvas_w]
|
||||||
|
|
||||||
# 6. 创建纯白色背景并合成
|
# 6. 创建纯白色背景并合成(保持你原来的风格)
|
||||||
# 创建一个纯白色的 BGR 画布
|
|
||||||
white_background = np.full((canvas_h, canvas_w, 3), 255, dtype=np.uint8)
|
white_background = np.full((canvas_h, canvas_w, 3), 255, dtype=np.uint8)
|
||||||
|
|
||||||
# 分离平铺层的颜色通道和 Alpha 通道
|
|
||||||
tiled_bgr = tiled_layer[:, :, :3]
|
tiled_bgr = tiled_layer[:, :, :3]
|
||||||
alpha_mask = tiled_layer[:, :, 3] / 255.0 # 归一化到 0-1
|
alpha_mask = tiled_layer[:, :, 3] / 255.0
|
||||||
alpha_mask = cv2.merge([alpha_mask, alpha_mask, alpha_mask]) # 扩展到 3 通道
|
alpha_mask = cv2.merge([alpha_mask, alpha_mask, alpha_mask])
|
||||||
|
|
||||||
# 执行 Alpha 混合:结果 = 平铺层 * alpha + 背景 * (1 - alpha)
|
tiled_print = (tiled_bgr * alpha_mask + white_background * (1 - alpha_mask)).astype(np.uint8)
|
||||||
result = (tiled_bgr * alpha_mask + white_background * (1 - alpha_mask)).astype(np.uint8)
|
|
||||||
|
|
||||||
return result
|
# ====================== 处理 Mask ======================
|
||||||
|
# Mask 也同样居中处理
|
||||||
|
resized_mask = cv2.resize(mask, dim, interpolation=cv2.INTER_NEAREST)
|
||||||
|
rotated_mask = rotate_image(resized_mask, angle) # 注意:mask也需要旋转
|
||||||
|
|
||||||
|
unit_mask = np.zeros((cell_h, cell_w), dtype=np.uint8)
|
||||||
|
unit_mask[start_y:start_y + p_h, start_x:start_x + p_w] = rotated_mask
|
||||||
|
|
||||||
|
full_mask_tiled = np.tile(unit_mask, (tiles_y, tiles_x))
|
||||||
|
tiled_mask = full_mask_tiled[offset_y: offset_y + canvas_h,
|
||||||
|
offset_x: offset_x + canvas_w]
|
||||||
|
|
||||||
|
return tiled_print, cv2.bitwise_not(tiled_mask)
|
||||||
|
|
||||||
|
|
||||||
def rotate_image(image, angle):
|
def rotate_image(image, angle):
|
||||||
|
|||||||
@@ -41,7 +41,7 @@ class PrintPainting:
|
|||||||
if overall_print['print_path_list']:
|
if overall_print['print_path_list']:
|
||||||
overall_print['location'][0] = [x * y for x, y in zip(overall_print['location'][0], result['resize_scale'])]
|
overall_print['location'][0] = [x * y for x, y in zip(overall_print['location'][0], result['resize_scale'])]
|
||||||
painting_dict = {'dim_image_h': result['pattern_image'].shape[0], 'dim_image_w': result['pattern_image'].shape[1]}
|
painting_dict = {'dim_image_h': result['pattern_image'].shape[0], 'dim_image_w': result['pattern_image'].shape[1]}
|
||||||
result['print_image'] = result['pattern_image']
|
result['print_image'] = result['pattern_image'].copy()
|
||||||
# 获取平铺 + 旋转 的overall print
|
# 获取平铺 + 旋转 的overall print
|
||||||
painting_dict = self.painting_collection(painting_dict, overall_print)
|
painting_dict = self.painting_collection(painting_dict, overall_print)
|
||||||
result['print_image'] = self.printpaint(result, painting_dict, print_=True)
|
result['print_image'] = self.printpaint(result, painting_dict, print_=True)
|
||||||
@@ -229,15 +229,15 @@ class PrintPainting:
|
|||||||
dim_max = max(painting_dict['dim_image_h'], painting_dict['dim_image_w'])
|
dim_max = max(painting_dict['dim_image_h'], painting_dict['dim_image_w'])
|
||||||
dim_pattern = (int(dim_max * print_['scale'] / 5), int(dim_max * print_['scale'] / 5))
|
dim_pattern = (int(dim_max * print_['scale'] / 5), int(dim_max * print_['scale'] / 5))
|
||||||
gap = print_dict.get('gap', [[0, 0]])[0]
|
gap = print_dict.get('gap', [[0, 0]])[0]
|
||||||
painting_dict['tile_print'] = tile_image(pattern=print_['image'],
|
painting_dict['tile_print'], painting_dict['mask_inv_print'] = tile_image(pattern=print_['image'],
|
||||||
dim=dim_pattern,
|
mask=print_['mask'],
|
||||||
gap_x=gap[0],
|
dim=dim_pattern,
|
||||||
gap_y=gap[1],
|
gap_x=gap[0],
|
||||||
canvas_h=painting_dict['dim_image_h'],
|
gap_y=gap[1],
|
||||||
canvas_w=painting_dict['dim_image_w'],
|
canvas_h=painting_dict['dim_image_h'],
|
||||||
location=painting_dict['location'],
|
canvas_w=painting_dict['dim_image_w'],
|
||||||
angle=int(print_.get('print_angle_list', [0])[0]))
|
location=painting_dict['location'],
|
||||||
painting_dict['mask_inv_print'] = np.zeros(painting_dict['tile_print'].shape[:2], dtype=np.uint8)
|
angle=int(print_.get('print_angle_list', [0])[0]))
|
||||||
return painting_dict
|
return painting_dict
|
||||||
|
|
||||||
def tile_image(self, pattern, dim, scale, dim_image_h, dim_image_w, location, trigger=False):
|
def tile_image(self, pattern, dim, scale, dim_image_h, dim_image_w, location, trigger=False):
|
||||||
@@ -318,10 +318,15 @@ class PrintPainting:
|
|||||||
image = oss_get_image(oss_client=self.minio_client, bucket=bucket_name, object_name=object_name, data_type="PIL")
|
image = oss_get_image(oss_client=self.minio_client, bucket=bucket_name, object_name=object_name, data_type="PIL")
|
||||||
# 判断图片格式,如果是RGBA 则贴在一张纯白图片上 防止透明转黑
|
# 判断图片格式,如果是RGBA 则贴在一张纯白图片上 防止透明转黑
|
||||||
if image.mode == "RGBA":
|
if image.mode == "RGBA":
|
||||||
|
mask_pil = image.split()[3]
|
||||||
new_background = Image.new('RGB', image.size, (255, 255, 255))
|
new_background = Image.new('RGB', image.size, (255, 255, 255))
|
||||||
new_background.paste(image, mask=image.split()[3])
|
new_background.paste(image, mask=image.split()[3])
|
||||||
image = new_background
|
image = new_background
|
||||||
|
else:
|
||||||
|
mask_pil = Image.new('L', image.size, 255) # L=灰度图,255=纯白
|
||||||
print_dict['image'] = cv2.cvtColor(np.asarray(image), cv2.COLOR_RGB2BGR)
|
print_dict['image'] = cv2.cvtColor(np.asarray(image), cv2.COLOR_RGB2BGR)
|
||||||
|
print_dict['mask'] = cv2.threshold(np.array(mask_pil), 127, 255, cv2.THRESH_BINARY)[1]
|
||||||
|
|
||||||
return print_dict
|
return print_dict
|
||||||
|
|
||||||
def crop_image(self, image, image_size_h, image_size_w, location, print_shape):
|
def crop_image(self, image, image_size_h, image_size_w, location, print_shape):
|
||||||
@@ -471,9 +476,12 @@ class PrintPainting:
|
|||||||
return cropped_img
|
return cropped_img
|
||||||
|
|
||||||
|
|
||||||
def tile_image(pattern, dim, gap_x, gap_y, canvas_h, canvas_w, location, angle=0):
|
def tile_image(pattern, mask, dim, gap_x, gap_y, canvas_h, canvas_w, location, angle=0):
|
||||||
"""
|
"""
|
||||||
按照指定的 X/Y 间距平铺印花,并支持旋转
|
按照指定的 X/Y 间距平铺印花,并支持旋转
|
||||||
|
【修改版】以被平铺图案的【中心】作为平铺基准点
|
||||||
|
|
||||||
|
:param location: [[center_y, center_x]] → 第一个图案中心的坐标
|
||||||
:param angle: 旋转角度 (度数, 逆时针)
|
:param angle: 旋转角度 (度数, 逆时针)
|
||||||
"""
|
"""
|
||||||
# 1. 确保输入是 RGBA
|
# 1. 确保输入是 RGBA
|
||||||
@@ -485,35 +493,54 @@ def tile_image(pattern, dim, gap_x, gap_y, canvas_h, canvas_w, location, angle=0
|
|||||||
rotated_p = rotate_image(resized_p, angle)
|
rotated_p = rotate_image(resized_p, angle)
|
||||||
p_h, p_w = rotated_p.shape[:2]
|
p_h, p_w = rotated_p.shape[:2]
|
||||||
|
|
||||||
# 3. 创建透明单元格
|
# 3. 创建透明单元格(图案放在单元格中心)
|
||||||
cell_h, cell_w = p_h + gap_y, p_w + gap_x
|
cell_h = p_h + gap_y
|
||||||
|
cell_w = p_w + gap_x
|
||||||
|
|
||||||
unit_cell = np.zeros((cell_h, cell_w, 4), dtype=np.uint8)
|
unit_cell = np.zeros((cell_h, cell_w, 4), dtype=np.uint8)
|
||||||
unit_cell[:p_h, :p_w, :] = rotated_p
|
|
||||||
|
# 计算图案在单元格中的左上角位置(让图案居中)
|
||||||
|
start_y = (cell_h - p_h) // 2
|
||||||
|
start_x = (cell_w - p_w) // 2
|
||||||
|
unit_cell[start_y:start_y + p_h, start_x:start_x + p_w, :] = rotated_p
|
||||||
|
|
||||||
# 4. 执行平铺
|
# 4. 执行平铺
|
||||||
tiles_y = (canvas_h // cell_h) + 2
|
tiles_y = (canvas_h // cell_h) + 3 # 多加一点余量更安全
|
||||||
tiles_x = (canvas_w // cell_w) + 2
|
tiles_x = (canvas_w // cell_w) + 3
|
||||||
full_tiled = np.tile(unit_cell, (tiles_y, tiles_x, 1))
|
full_tiled = np.tile(unit_cell, (tiles_y, tiles_x, 1))
|
||||||
|
|
||||||
# 5. 裁剪平铺层
|
# 5. 计算偏移(关键修改:以中心为基准)
|
||||||
offset_x = int(location[0][1] % cell_w)
|
center_y, center_x = location[0][0], location[0][1] # 第一个图案的中心位置
|
||||||
offset_y = int(location[0][0] % cell_h)
|
|
||||||
|
# 计算从哪个位置开始裁剪,才能让中心落在指定坐标
|
||||||
|
offset_y = int((center_y - (p_h // 2)) % cell_h)
|
||||||
|
offset_x = int((center_x - (p_w // 2)) % cell_w)
|
||||||
|
|
||||||
tiled_layer = full_tiled[offset_y: offset_y + canvas_h,
|
tiled_layer = full_tiled[offset_y: offset_y + canvas_h,
|
||||||
offset_x: offset_x + canvas_w]
|
offset_x: offset_x + canvas_w]
|
||||||
|
|
||||||
# 6. 创建纯白色背景并合成
|
# 6. 创建纯白色背景并合成(保持你原来的风格)
|
||||||
# 创建一个纯白色的 BGR 画布
|
|
||||||
white_background = np.full((canvas_h, canvas_w, 3), 255, dtype=np.uint8)
|
white_background = np.full((canvas_h, canvas_w, 3), 255, dtype=np.uint8)
|
||||||
|
|
||||||
# 分离平铺层的颜色通道和 Alpha 通道
|
|
||||||
tiled_bgr = tiled_layer[:, :, :3]
|
tiled_bgr = tiled_layer[:, :, :3]
|
||||||
alpha_mask = tiled_layer[:, :, 3] / 255.0 # 归一化到 0-1
|
alpha_mask = tiled_layer[:, :, 3] / 255.0
|
||||||
alpha_mask = cv2.merge([alpha_mask, alpha_mask, alpha_mask]) # 扩展到 3 通道
|
alpha_mask = cv2.merge([alpha_mask, alpha_mask, alpha_mask])
|
||||||
|
|
||||||
# 执行 Alpha 混合:结果 = 平铺层 * alpha + 背景 * (1 - alpha)
|
tiled_print = (tiled_bgr * alpha_mask + white_background * (1 - alpha_mask)).astype(np.uint8)
|
||||||
result = (tiled_bgr * alpha_mask + white_background * (1 - alpha_mask)).astype(np.uint8)
|
|
||||||
|
|
||||||
return result
|
# ====================== 处理 Mask ======================
|
||||||
|
# Mask 也同样居中处理
|
||||||
|
resized_mask = cv2.resize(mask, dim, interpolation=cv2.INTER_NEAREST)
|
||||||
|
rotated_mask = rotate_image(resized_mask, angle) # 注意:mask也需要旋转
|
||||||
|
|
||||||
|
unit_mask = np.zeros((cell_h, cell_w), dtype=np.uint8)
|
||||||
|
unit_mask[start_y:start_y + p_h, start_x:start_x + p_w] = rotated_mask
|
||||||
|
|
||||||
|
full_mask_tiled = np.tile(unit_mask, (tiles_y, tiles_x))
|
||||||
|
tiled_mask = full_mask_tiled[offset_y: offset_y + canvas_h,
|
||||||
|
offset_x: offset_x + canvas_w]
|
||||||
|
|
||||||
|
return tiled_print, cv2.bitwise_not(tiled_mask)
|
||||||
|
|
||||||
|
|
||||||
def rotate_image(image, angle):
|
def rotate_image(image, angle):
|
||||||
|
|||||||
35
app/service/sketch2garment/callback.py
Normal file
35
app/service/sketch2garment/callback.py
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
import logging
|
||||||
|
|
||||||
|
import httpx
|
||||||
|
|
||||||
|
logger = logging.getLogger("app")
|
||||||
|
|
||||||
|
|
||||||
|
async def notify_callback(callback_url: str, task_id: str, status: str, result: dict, ):
|
||||||
|
"""
|
||||||
|
调用客户端提供的回调接口
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
payload = {
|
||||||
|
"task_id": task_id,
|
||||||
|
"status": status,
|
||||||
|
"result": result
|
||||||
|
}
|
||||||
|
logger.info(payload)
|
||||||
|
async with httpx.AsyncClient(timeout=30.0) as client:
|
||||||
|
resp = await client.post(
|
||||||
|
str(callback_url),
|
||||||
|
json=payload,
|
||||||
|
headers={"Content-Type": "application/json"}
|
||||||
|
)
|
||||||
|
|
||||||
|
if 200 <= resp.status_code < 300:
|
||||||
|
logger.info(f"回调成功 | task_id: {task_id} | status: {status} | url: {callback_url}")
|
||||||
|
return True
|
||||||
|
else:
|
||||||
|
logger.warning(f"回调返回非2xx状态码 | task_id: {task_id} | status: {resp.status_code} | url: {callback_url}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"回调失败 | task_id: {task_id} | url: {callback_url} | error: {e}", exc_info=True)
|
||||||
|
return False
|
||||||
46
app/service/sketch2garment/celery_app.py
Normal file
46
app/service/sketch2garment/celery_app.py
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
from celery import Celery
|
||||||
|
from kombu import Queue, Exchange
|
||||||
|
|
||||||
|
from app.core.config import settings
|
||||||
|
|
||||||
|
celery_app = Celery(
|
||||||
|
"sketch_to_garment",
|
||||||
|
broker=f"redis://{settings.REDIS_HOST}:{settings.REDIS_PORT}/2",
|
||||||
|
backend=f"redis://{settings.REDIS_HOST}:{settings.REDIS_PORT}/{settings.REDIS_DB}",
|
||||||
|
include=["app.service.sketch2garment.tasks"]
|
||||||
|
)
|
||||||
|
print(f"redis://{settings.REDIS_HOST}:{settings.REDIS_PORT}/3")
|
||||||
|
print(f"celery_app: {celery_app}")
|
||||||
|
|
||||||
|
celery_app.conf.update(
|
||||||
|
task_serializer="json",
|
||||||
|
accept_content=["json"],
|
||||||
|
result_serializer="json",
|
||||||
|
timezone="Asia/Hong_Kong",
|
||||||
|
enable_utc=True,
|
||||||
|
task_track_started=True,
|
||||||
|
task_time_limit=300, # 单个任务最长 5 分钟
|
||||||
|
task_soft_time_limit=280,
|
||||||
|
# 定义队列
|
||||||
|
task_queues=(
|
||||||
|
Queue("sketch_to_garment_queue",
|
||||||
|
exchange=Exchange("sketch_to_garment_exchange", type="direct"),
|
||||||
|
durable=True),
|
||||||
|
|
||||||
|
),
|
||||||
|
|
||||||
|
task_routes={
|
||||||
|
'app.service.sketch2garment.tasks.sketch_to_garment':
|
||||||
|
{
|
||||||
|
'queue': 'sketch_to_garment_queue',
|
||||||
|
'exchange': 'sketch_to_garment_exchange', # ← 修改这里
|
||||||
|
},
|
||||||
|
},
|
||||||
|
task_default_queue="sketch_to_garment_queue",
|
||||||
|
|
||||||
|
worker_concurrency=1,
|
||||||
|
worker_prefetch_multiplier=1,
|
||||||
|
worker_max_tasks_per_child=1,
|
||||||
|
task_acks_late=True,
|
||||||
|
task_reject_on_worker_lost=True,
|
||||||
|
)
|
||||||
44
app/service/sketch2garment/server.py
Normal file
44
app/service/sketch2garment/server.py
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
import logging
|
||||||
|
|
||||||
|
from app.service.sketch2garment.tasks import sketch_to_garment
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
def submit_sketch_to_garment_task(model: str = "single", task_id: str = "", callback_url: str = "", bucket_name: str = "test", user_id: str = "123", input_image_path: str = ""):
|
||||||
|
"""提交 img_to_3D 任务(带队列长度限制)"""
|
||||||
|
queue_name = "img_to_3d_queue"
|
||||||
|
max_queue_length = 10
|
||||||
|
|
||||||
|
try:
|
||||||
|
# current_length = get_queue_length(queue_name)
|
||||||
|
|
||||||
|
# if current_length >= max_queue_length:
|
||||||
|
# return {
|
||||||
|
# "state": "queue_full",
|
||||||
|
# "message": "当前 3D 生成请求较多,请稍后重试。",
|
||||||
|
# "queue_length": current_length,
|
||||||
|
# "max_length": max_queue_length
|
||||||
|
# }
|
||||||
|
|
||||||
|
# 提交任务
|
||||||
|
task = sketch_to_garment.apply_async(
|
||||||
|
args=(task_id, callback_url, bucket_name, input_image_path, user_id, model),
|
||||||
|
task_id=task_id,
|
||||||
|
queue="sketch_to_garment_queue")
|
||||||
|
|
||||||
|
# logger.info(f"img_to_3d_task 已提交 | task_id: {task_id} | 当前队列长度: {current_length}")
|
||||||
|
|
||||||
|
return {
|
||||||
|
"state": "success",
|
||||||
|
"task_id": task_id,
|
||||||
|
"message": "任务已成功提交,正在后台处理...",
|
||||||
|
}
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"提交 img_to_3d_task 失败: {e}", exc_info=True)
|
||||||
|
return {
|
||||||
|
"state": "fail",
|
||||||
|
"message": "提交失败,请稍后重试。",
|
||||||
|
"error": str(e)
|
||||||
|
}
|
||||||
57
app/service/sketch2garment/tasks.py
Normal file
57
app/service/sketch2garment/tasks.py
Normal file
@@ -0,0 +1,57 @@
|
|||||||
|
import asyncio
|
||||||
|
import logging
|
||||||
|
|
||||||
|
from app.core.config import settings
|
||||||
|
from app.service.sketch2garment.callback import notify_callback
|
||||||
|
import httpx
|
||||||
|
|
||||||
|
from app.service.sketch2garment.celery_app import celery_app
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
@celery_app.task(bind=True, queue="sketch_to_garment_queue", max_retries=3, name='app.service.sketch2garment.tasks.sketch_to_garment')
|
||||||
|
def sketch_to_garment(self, task_id: str, callback_url: str, bucket_name: str, input_image_path: str, user_id: str, category: str = None):
|
||||||
|
payload = {
|
||||||
|
"bucket_name": bucket_name,
|
||||||
|
"category": category or settings.DEFAULT_CATEGORY,
|
||||||
|
"input_image_path": input_image_path,
|
||||||
|
"user_id": user_id
|
||||||
|
}
|
||||||
|
logger.info(f"payload: {payload}")
|
||||||
|
|
||||||
|
try:
|
||||||
|
with httpx.Client(timeout=300.0) as client: # 注意这里用 AsyncClient 配合 Celery
|
||||||
|
# 如果你的 LitServe 是同步 endpoint,也可以用 httpx.Client()
|
||||||
|
response = client.post(settings.SKETCH_TO_GARMENT_URL, json=payload)
|
||||||
|
if response.status_code == 200:
|
||||||
|
result = response.json()
|
||||||
|
result_json = {
|
||||||
|
"pattern": result[1],
|
||||||
|
"texture": result[2],
|
||||||
|
"glb": result[3],
|
||||||
|
"texture_fabric": result[4]
|
||||||
|
}
|
||||||
|
asyncio.run(
|
||||||
|
notify_callback(callback_url=callback_url, task_id=task_id, result=result_json, status="success")
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
asyncio.run(
|
||||||
|
notify_callback(
|
||||||
|
callback_url=callback_url,
|
||||||
|
task_id=task_id,
|
||||||
|
result={
|
||||||
|
"status": "fail",
|
||||||
|
"task_id": task_id,
|
||||||
|
"message": "fail",
|
||||||
|
"error": "fail"
|
||||||
|
},
|
||||||
|
status="fail")
|
||||||
|
)
|
||||||
|
except Exception as e:
|
||||||
|
return {
|
||||||
|
"status": "failed",
|
||||||
|
"task_id": task_id,
|
||||||
|
"input": payload,
|
||||||
|
"error": str(e)
|
||||||
|
}
|
||||||
@@ -24,6 +24,7 @@ dependencies = [
|
|||||||
"loguru>=0.7.3",
|
"loguru>=0.7.3",
|
||||||
"minio>=7.2.20",
|
"minio>=7.2.20",
|
||||||
"moviepy==1.0.3",
|
"moviepy==1.0.3",
|
||||||
|
"nacos-sdk-python==2.0.1",
|
||||||
"np>=1.0.2",
|
"np>=1.0.2",
|
||||||
"numpy<2",
|
"numpy<2",
|
||||||
"ollama>=0.6.1",
|
"ollama>=0.6.1",
|
||||||
|
|||||||
195
uv.lock
generated
195
uv.lock
generated
@@ -62,6 +62,15 @@ wheels = [
|
|||||||
{ url = "https://files.pythonhosted.org/packages/7c/91/513971861d845d28160ecb205ae2cfaf618b16918a9cd4e0b832b5360ce7/aio_pika-9.5.8-py3-none-any.whl", hash = "sha256:f4c6cb8a6c5176d00f39fd7431e9702e638449bc6e86d1769ad7548b2a506a8d", size = 54397, upload-time = "2025-11-12T10:37:08.374Z" },
|
{ url = "https://files.pythonhosted.org/packages/7c/91/513971861d845d28160ecb205ae2cfaf618b16918a9cd4e0b832b5360ce7/aio_pika-9.5.8-py3-none-any.whl", hash = "sha256:f4c6cb8a6c5176d00f39fd7431e9702e638449bc6e86d1769ad7548b2a506a8d", size = 54397, upload-time = "2025-11-12T10:37:08.374Z" },
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "aiofiles"
|
||||||
|
version = "25.1.0"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/41/c3/534eac40372d8ee36ef40df62ec129bee4fdb5ad9706e58a29be53b2c970/aiofiles-25.1.0.tar.gz", hash = "sha256:a8d728f0a29de45dc521f18f07297428d56992a742f0cd2701ba86e44d23d5b2", size = 46354, upload-time = "2025-10-09T20:51:04.358Z" }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/bc/8a/340a1555ae33d7354dbca4faa54948d76d89a27ceef032c8c3bc661d003e/aiofiles-25.1.0-py3-none-any.whl", hash = "sha256:abe311e527c862958650f9438e859c1fa7568a141b22abcd015e120e86a85695", size = 14668, upload-time = "2025-10-09T20:51:03.174Z" },
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "aiohappyeyeballs"
|
name = "aiohappyeyeballs"
|
||||||
version = "2.6.1"
|
version = "2.6.1"
|
||||||
@@ -131,6 +140,154 @@ wheels = [
|
|||||||
{ url = "https://files.pythonhosted.org/packages/fb/76/641ae371508676492379f16e2fa48f4e2c11741bd63c48be4b12a6b09cba/aiosignal-1.4.0-py3-none-any.whl", hash = "sha256:053243f8b92b990551949e63930a839ff0cf0b0ebbe0597b0f3fb19e1a0fe82e", size = 7490, upload-time = "2025-07-03T22:54:42.156Z" },
|
{ url = "https://files.pythonhosted.org/packages/fb/76/641ae371508676492379f16e2fa48f4e2c11741bd63c48be4b12a6b09cba/aiosignal-1.4.0-py3-none-any.whl", hash = "sha256:053243f8b92b990551949e63930a839ff0cf0b0ebbe0597b0f3fb19e1a0fe82e", size = 7490, upload-time = "2025-07-03T22:54:42.156Z" },
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "alibabacloud-credentials"
|
||||||
|
version = "0.3.6"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
dependencies = [
|
||||||
|
{ name = "alibabacloud-tea" },
|
||||||
|
]
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/fc/92/7cb0807d6d380fa09cbad6d4fe983781e657dcc16d60fc559d6575bf98be/alibabacloud_credentials-0.3.6.tar.gz", hash = "sha256:caa82cf258648dcbe1ca14aeba50ba21845567d6ac3cd48d318e0a445fff7f96", size = 18771, upload-time = "2024-10-28T03:40:03.806Z" }
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "alibabacloud-darabonba-array"
|
||||||
|
version = "0.1.0"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/50/be/1813d7553e11e20a1422ffaaead392cfa7239a855c7e67c6a6b5776cfa64/alibabacloud_darabonba_array-0.1.0.tar.gz", hash = "sha256:7f9a7c632518ff4f0cebb0d4e825a48c12e7cf0b9016ea25054dd73732e155aa", size = 2306, upload-time = "2022-11-01T06:32:47.928Z" }
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "alibabacloud-darabonba-encode-util"
|
||||||
|
version = "0.0.2"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/b6/d8/22543b2ade9aa68fef028a9f0c4154bfdb970926f918f63d7b85bae527a9/alibabacloud_darabonba_encode_util-0.0.2.tar.gz", hash = "sha256:f1c484f276d60450fa49b4b2987194e741fcb2f7faae7f287c0ae65abc85fd4d", size = 3990, upload-time = "2022-12-10T04:43:48.086Z" }
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "alibabacloud-darabonba-map"
|
||||||
|
version = "0.0.1"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/d5/bc/f11d56adffffade9a0d33ccca155ce82ca950b97cdce27a75228715c4639/alibabacloud_darabonba_map-0.0.1.tar.gz", hash = "sha256:adb17384658a1a8f72418f1838d4b6a5fd2566bfd392a3ef06d9dbb0a595a23f", size = 2056, upload-time = "2021-12-04T03:41:20.369Z" }
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "alibabacloud-darabonba-signature-util"
|
||||||
|
version = "0.0.4"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
dependencies = [
|
||||||
|
{ name = "cryptography" },
|
||||||
|
]
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/13/09/2118a2df631eaa06a291013ea61f31e449ba7a3cc3d6061477a43420e93a/alibabacloud_darabonba_signature_util-0.0.4.tar.gz", hash = "sha256:71d79b2ae65957bcfbf699ced894fda782b32f9635f1616635533e5a90d5feb0", size = 4170, upload-time = "2022-12-10T04:44:42.979Z" }
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "alibabacloud-darabonba-string"
|
||||||
|
version = "0.0.4"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/f9/d4/3d22bd2ff88985f970a10f8cedf2ea326d11d4d95e244f7665dc83d26465/alibabacloud-darabonba-string-0.0.4.tar.gz", hash = "sha256:ec6614c0448dadcbc5e466485838a1f8cfdd911135bea739e20b14511270c6f7", size = 2852, upload-time = "2021-12-13T13:30:06.114Z" }
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "alibabacloud-endpoint-util"
|
||||||
|
version = "0.0.4"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/92/7d/8cc92a95c920e344835b005af6ea45a0db98763ad6ad19299d26892e6c8d/alibabacloud_endpoint_util-0.0.4.tar.gz", hash = "sha256:a593eb8ddd8168d5dc2216cd33111b144f9189fcd6e9ca20e48f358a739bbf90", size = 2813, upload-time = "2025-06-12T07:20:52.572Z" }
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "alibabacloud-gateway-pop"
|
||||||
|
version = "0.0.9"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
dependencies = [
|
||||||
|
{ name = "alibabacloud-credentials" },
|
||||||
|
{ name = "alibabacloud-darabonba-array" },
|
||||||
|
{ name = "alibabacloud-darabonba-encode-util" },
|
||||||
|
{ name = "alibabacloud-darabonba-map" },
|
||||||
|
{ name = "alibabacloud-darabonba-signature-util" },
|
||||||
|
{ name = "alibabacloud-darabonba-string" },
|
||||||
|
{ name = "alibabacloud-endpoint-util" },
|
||||||
|
{ name = "alibabacloud-gateway-spi" },
|
||||||
|
{ name = "alibabacloud-openapi-util" },
|
||||||
|
{ name = "alibabacloud-tea-util" },
|
||||||
|
]
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/18/7d/d521d803ee227499aa5a3044a0ab8bd4ba139a455d10c1a070e745d26b0c/alibabacloud_gateway_pop-0.0.9.tar.gz", hash = "sha256:50aec34abc47b3adc6e43da6fa036bbbd04477a0047435f3728129ede7641628", size = 5981, upload-time = "2025-07-23T07:06:06.717Z" }
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "alibabacloud-gateway-spi"
|
||||||
|
version = "0.0.3"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
dependencies = [
|
||||||
|
{ name = "alibabacloud-credentials" },
|
||||||
|
]
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/ab/98/d7111245f17935bf72ee9bea60bbbeff2bc42cdfe24d2544db52bc517e1a/alibabacloud_gateway_spi-0.0.3.tar.gz", hash = "sha256:10d1c53a3fc5f87915fbd6b4985b98338a776e9b44a0263f56643c5048223b8b", size = 4249, upload-time = "2025-02-23T16:29:54.222Z" }
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "alibabacloud-kms20160120"
|
||||||
|
version = "2.2.3"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
dependencies = [
|
||||||
|
{ name = "alibabacloud-endpoint-util" },
|
||||||
|
{ name = "alibabacloud-gateway-pop" },
|
||||||
|
{ name = "alibabacloud-openapi-util" },
|
||||||
|
{ name = "alibabacloud-tea-openapi" },
|
||||||
|
{ name = "alibabacloud-tea-util" },
|
||||||
|
]
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/18/39/dfb1043f2995523507b03bb23e5db6291508eccbb4f78ea02930ff95f137/alibabacloud_kms20160120-2.2.3.tar.gz", hash = "sha256:fa7991185e92d85f9d224ead0bf82e5673fcfd022714e6c3cd2b1894b59555bf", size = 77350, upload-time = "2024-08-30T10:18:44.012Z" }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/cc/04/0668cbc62f3d9239e86d3d97b3de40b92e66730a90fc4c58f0ee38a81399/alibabacloud_kms20160120-2.2.3-py3-none-any.whl", hash = "sha256:51d3d04c75ba93c574ff4e368e51097f180fc05922fbd6336d290ea8113da99e", size = 76701, upload-time = "2024-08-30T10:18:42.524Z" },
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "alibabacloud-openapi-util"
|
||||||
|
version = "0.2.4"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
dependencies = [
|
||||||
|
{ name = "alibabacloud-tea-util" },
|
||||||
|
{ name = "cryptography" },
|
||||||
|
]
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/f6/51/be5802851a4ed20ac2c6db50ac8354a6e431e93db6e714ca39b50983626f/alibabacloud_openapi_util-0.2.4.tar.gz", hash = "sha256:87022b9dcb7593a601f7a40ca698227ac3ccb776b58cb7b06b8dc7f510995c34", size = 7981, upload-time = "2026-01-15T08:05:03.947Z" }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/08/46/9b217343648b366eb93447f5d93116e09a61956005794aed5ef95a2e9e2e/alibabacloud_openapi_util-0.2.4-py3-none-any.whl", hash = "sha256:a2474f230b5965ae9a8c286e0dc86132a887928d02d20b8182656cf6b1b6c5bd", size = 7661, upload-time = "2026-01-15T08:05:01.374Z" },
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "alibabacloud-tea"
|
||||||
|
version = "0.4.3"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
dependencies = [
|
||||||
|
{ name = "aiohttp" },
|
||||||
|
{ name = "requests" },
|
||||||
|
]
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/9a/7d/b22cb9a0d4f396ee0f3f9d7f26b76b9ed93d4101add7867a2c87ed2534f5/alibabacloud-tea-0.4.3.tar.gz", hash = "sha256:ec8053d0aa8d43ebe1deb632d5c5404339b39ec9a18a0707d57765838418504a", size = 8785, upload-time = "2025-03-24T07:34:42.958Z" }
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "alibabacloud-tea-openapi"
|
||||||
|
version = "0.3.14"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
dependencies = [
|
||||||
|
{ name = "alibabacloud-credentials" },
|
||||||
|
{ name = "alibabacloud-gateway-spi" },
|
||||||
|
{ name = "alibabacloud-openapi-util" },
|
||||||
|
{ name = "alibabacloud-tea-util" },
|
||||||
|
{ name = "alibabacloud-tea-xml" },
|
||||||
|
]
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/ff/f5/c7823490a1574d1e3c27c9641aa395710e89d0c15c5a436c96e999e6e2fe/alibabacloud_tea_openapi-0.3.14.tar.gz", hash = "sha256:1e0a67ab3450cf09e26ccc0fb5b0622a6b58fdde25dc3ccb99b45e167c5db717", size = 12993, upload-time = "2025-04-15T12:20:03.363Z" }
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "alibabacloud-tea-util"
|
||||||
|
version = "0.3.14"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
dependencies = [
|
||||||
|
{ name = "alibabacloud-tea" },
|
||||||
|
]
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/e9/ee/ea90be94ad781a5055db29556744681fc71190ef444ae53adba45e1be5f3/alibabacloud_tea_util-0.3.14.tar.gz", hash = "sha256:708e7c9f64641a3c9e0e566365d2f23675f8d7c2a3e2971d9402ceede0408cdb", size = 7515, upload-time = "2025-11-19T06:01:08.504Z" }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/72/9e/c394b4e2104766fb28a1e44e3ed36e4c7773b4d05c868e482be99d5635c9/alibabacloud_tea_util-0.3.14-py3-none-any.whl", hash = "sha256:10d3e5c340d8f7ec69dd27345eb2fc5a1dab07875742525edf07bbe86db93bfe", size = 6697, upload-time = "2025-11-19T06:01:07.355Z" },
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "alibabacloud-tea-xml"
|
||||||
|
version = "0.0.3"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
dependencies = [
|
||||||
|
{ name = "alibabacloud-tea" },
|
||||||
|
]
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/32/eb/5e82e419c3061823f3feae9b5681588762929dc4da0176667297c2784c1a/alibabacloud_tea_xml-0.0.3.tar.gz", hash = "sha256:979cb51fadf43de77f41c69fc69c12529728919f849723eb0cd24eb7b048a90c", size = 3466, upload-time = "2025-07-01T08:04:55.144Z" }
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "amqp"
|
name = "amqp"
|
||||||
version = "5.3.1"
|
version = "5.3.1"
|
||||||
@@ -1746,6 +1903,26 @@ wheels = [
|
|||||||
{ url = "https://files.pythonhosted.org/packages/79/7b/2c79738432f5c924bef5071f933bcc9efd0473bac3b4aa584a6f7c1c8df8/mypy_extensions-1.1.0-py3-none-any.whl", hash = "sha256:1be4cccdb0f2482337c4743e60421de3a356cd97508abadd57d47403e94f5505", size = 4963, upload-time = "2025-04-22T14:54:22.983Z" },
|
{ url = "https://files.pythonhosted.org/packages/79/7b/2c79738432f5c924bef5071f933bcc9efd0473bac3b4aa584a6f7c1c8df8/mypy_extensions-1.1.0-py3-none-any.whl", hash = "sha256:1be4cccdb0f2482337c4743e60421de3a356cd97508abadd57d47403e94f5505", size = 4963, upload-time = "2025-04-22T14:54:22.983Z" },
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "nacos-sdk-python"
|
||||||
|
version = "2.0.1"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
dependencies = [
|
||||||
|
{ name = "aiofiles" },
|
||||||
|
{ name = "aiohttp" },
|
||||||
|
{ name = "alibabacloud-kms20160120" },
|
||||||
|
{ name = "alibabacloud-tea-openapi" },
|
||||||
|
{ name = "grpcio" },
|
||||||
|
{ name = "protobuf" },
|
||||||
|
{ name = "psutil" },
|
||||||
|
{ name = "pycryptodome" },
|
||||||
|
{ name = "pydantic" },
|
||||||
|
]
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/9d/e4/c9506551fe699e1f0bc194a9024cc8fb18c8d4ee4f004dfdd5861db07b2d/nacos-sdk-python-2.0.1.tar.gz", hash = "sha256:29fa1dd14f771824b65ae0edd208bb4d20737655ae8b809685194e2f6358c2a7", size = 68582, upload-time = "2025-01-13T14:37:22.981Z" }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/e6/14/269a08582090ac1d16ff2c491455a22d4a4c4f47337eb0b142957a93ea0a/nacos_sdk_python-2.0.1-py3-none-any.whl", hash = "sha256:623cfc4645adb44f21c8613d6c0e6f1c41a0110318ce0899d57942009b626044", size = 93265, upload-time = "2025-01-13T14:37:17.808Z" },
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "networkx"
|
name = "networkx"
|
||||||
version = "3.6.1"
|
version = "3.6.1"
|
||||||
@@ -2307,6 +2484,22 @@ wheels = [
|
|||||||
{ url = "https://files.pythonhosted.org/packages/7e/cc/7e77861000a0691aeea8f4566e5d3aa716f2b1dece4a24439437e41d3d25/protobuf-5.29.5-py3-none-any.whl", hash = "sha256:6cf42630262c59b2d8de33954443d94b746c952b01434fc58a417fdbd2e84bd5", size = 172823, upload-time = "2025-05-28T23:51:58.157Z" },
|
{ url = "https://files.pythonhosted.org/packages/7e/cc/7e77861000a0691aeea8f4566e5d3aa716f2b1dece4a24439437e41d3d25/protobuf-5.29.5-py3-none-any.whl", hash = "sha256:6cf42630262c59b2d8de33954443d94b746c952b01434fc58a417fdbd2e84bd5", size = 172823, upload-time = "2025-05-28T23:51:58.157Z" },
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "psutil"
|
||||||
|
version = "7.2.2"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/aa/c6/d1ddf4abb55e93cebc4f2ed8b5d6dbad109ecb8d63748dd2b20ab5e57ebe/psutil-7.2.2.tar.gz", hash = "sha256:0746f5f8d406af344fd547f1c8daa5f5c33dbc293bb8d6a16d80b4bb88f59372", size = 493740, upload-time = "2026-01-28T18:14:54.428Z" }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/e7/36/5ee6e05c9bd427237b11b3937ad82bb8ad2752d72c6969314590dd0c2f6e/psutil-7.2.2-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:ed0cace939114f62738d808fdcecd4c869222507e266e574799e9c0faa17d486", size = 129090, upload-time = "2026-01-28T18:15:22.168Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/80/c4/f5af4c1ca8c1eeb2e92ccca14ce8effdeec651d5ab6053c589b074eda6e1/psutil-7.2.2-cp36-abi3-macosx_11_0_arm64.whl", hash = "sha256:1a7b04c10f32cc88ab39cbf606e117fd74721c831c98a27dc04578deb0c16979", size = 129859, upload-time = "2026-01-28T18:15:23.795Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/b5/70/5d8df3b09e25bce090399cf48e452d25c935ab72dad19406c77f4e828045/psutil-7.2.2-cp36-abi3-manylinux2010_x86_64.manylinux_2_12_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:076a2d2f923fd4821644f5ba89f059523da90dc9014e85f8e45a5774ca5bc6f9", size = 155560, upload-time = "2026-01-28T18:15:25.976Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/63/65/37648c0c158dc222aba51c089eb3bdfa238e621674dc42d48706e639204f/psutil-7.2.2-cp36-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b0726cecd84f9474419d67252add4ac0cd9811b04d61123054b9fb6f57df6e9e", size = 156997, upload-time = "2026-01-28T18:15:27.794Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/8e/13/125093eadae863ce03c6ffdbae9929430d116a246ef69866dad94da3bfbc/psutil-7.2.2-cp36-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:fd04ef36b4a6d599bbdb225dd1d3f51e00105f6d48a28f006da7f9822f2606d8", size = 148972, upload-time = "2026-01-28T18:15:29.342Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/04/78/0acd37ca84ce3ddffaa92ef0f571e073faa6d8ff1f0559ab1272188ea2be/psutil-7.2.2-cp36-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:b58fabe35e80b264a4e3bb23e6b96f9e45a3df7fb7eed419ac0e5947c61e47cc", size = 148266, upload-time = "2026-01-28T18:15:31.597Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/b4/90/e2159492b5426be0c1fef7acba807a03511f97c5f86b3caeda6ad92351a7/psutil-7.2.2-cp37-abi3-win_amd64.whl", hash = "sha256:eb7e81434c8d223ec4a219b5fc1c47d0417b12be7ea866e24fb5ad6e84b3d988", size = 137737, upload-time = "2026-01-28T18:15:33.849Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/8c/c7/7bb2e321574b10df20cbde462a94e2b71d05f9bbda251ef27d104668306a/psutil-7.2.2-cp37-abi3-win_arm64.whl", hash = "sha256:8c233660f575a5a89e6d4cb65d9f938126312bca76d8fe087b947b3a1aaac9ee", size = 134617, upload-time = "2026-01-28T18:15:36.514Z" },
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "psycopg2-binary"
|
name = "psycopg2-binary"
|
||||||
version = "2.9.11"
|
version = "2.9.11"
|
||||||
@@ -3165,6 +3358,7 @@ dependencies = [
|
|||||||
{ name = "loguru" },
|
{ name = "loguru" },
|
||||||
{ name = "minio" },
|
{ name = "minio" },
|
||||||
{ name = "moviepy" },
|
{ name = "moviepy" },
|
||||||
|
{ name = "nacos-sdk-python" },
|
||||||
{ name = "np" },
|
{ name = "np" },
|
||||||
{ name = "numpy" },
|
{ name = "numpy" },
|
||||||
{ name = "ollama" },
|
{ name = "ollama" },
|
||||||
@@ -3216,6 +3410,7 @@ requires-dist = [
|
|||||||
{ name = "loguru", specifier = ">=0.7.3" },
|
{ name = "loguru", specifier = ">=0.7.3" },
|
||||||
{ name = "minio", specifier = ">=7.2.20" },
|
{ name = "minio", specifier = ">=7.2.20" },
|
||||||
{ name = "moviepy", specifier = "==1.0.3" },
|
{ name = "moviepy", specifier = "==1.0.3" },
|
||||||
|
{ name = "nacos-sdk-python", specifier = "==2.0.1" },
|
||||||
{ name = "np", specifier = ">=1.0.2" },
|
{ name = "np", specifier = ">=1.0.2" },
|
||||||
{ name = "numpy", specifier = "<2" },
|
{ name = "numpy", specifier = "<2" },
|
||||||
{ name = "ollama", specifier = ">=0.6.1" },
|
{ name = "ollama", specifier = ">=0.6.1" },
|
||||||
|
|||||||
Reference in New Issue
Block a user