refactor(接口3): 简化为只需划线图一张,去掉 original + best_hairline
按需求方意见——B端只需上传一张已画好发际线的图,用不着原图: - 入参去掉 original_image_*,只保留 marked_image_*(三选一) - 输出去掉 best_hairline_image_url,只返回 hair_growth_image_url + hairline_type - ComfyUI 输入图改用 marked 划线图原样(add_hair.json 本就是"画了线的照片", 提示词清除黑线);检测路径只用于建遮罩,不再重画干净线/不需对齐原图 - service.generate_grow_b 签名改 (marked_bgr) 单参 - 同步文档:接口文档/接口3技术方案/网关映射表(去掉接口3 best_hairline 行) - 测试更新:grow-b 只传 marked,断言无 best_hairline 字段,44全绿 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -538,16 +538,11 @@ async def hair_grow(
|
|||||||
summary="接口3 B端生发(医生/操作端)",
|
summary="接口3 B端生发(医生/操作端)",
|
||||||
tags=["生发"],
|
tags=["生发"],
|
||||||
description="""
|
description="""
|
||||||
医生/操作端在用户原图上**手动划线标注**目标发际线后,上传划线图与原图,返回:
|
医生/操作端在用户照片上**手动用马克笔划线标注**目标发际线后,**只需上传这一张划线图**,返回:
|
||||||
- 最合适的发际线图片 URL
|
- 生发后效果图(系统检测划线 → 据此生成「植发 3 个月」效果)
|
||||||
- 生发后效果图 URL
|
- 发际线形(手绘为定制,固定 `custom`)
|
||||||
- 发际线形
|
|
||||||
|
|
||||||
**划线图片**:字段名前缀为 `marked_image_`,同样支持文件/URL/base64 三选一。
|
**划线图片**:字段名前缀为 `marked_image_`,支持文件/URL/base64 三选一。**不需要原始照片**。
|
||||||
|
|
||||||
**原始用户照片**:字段名前缀为 `original_image_`,同样支持文件/URL/base64 三选一,**必填**。
|
|
||||||
|
|
||||||
> 两组图片的传参方式可以不同,例如划线图用 URL,原图用 base64,但各自组内严格互斥。
|
|
||||||
""",
|
""",
|
||||||
responses={
|
responses={
|
||||||
200: {
|
200: {
|
||||||
@@ -559,7 +554,6 @@ async def hair_grow(
|
|||||||
"message": "success",
|
"message": "success",
|
||||||
"request_id": "mock-request-id",
|
"request_id": "mock-request-id",
|
||||||
"data": {
|
"data": {
|
||||||
"best_hairline_image_base64": "iVBORw0KGgo...(原图)",
|
|
||||||
"hair_growth_image_base64": "iVBORw0KGgo...(生发图)",
|
"hair_growth_image_base64": "iVBORw0KGgo...(生发图)",
|
||||||
"hairline_type": "custom",
|
"hairline_type": "custom",
|
||||||
},
|
},
|
||||||
@@ -581,23 +575,16 @@ async def hair_grow_b(
|
|||||||
marked_image_file: Optional[UploadFile] = File(default=None, description="划线图片文件(JPG/PNG,≤ 1 MB)"),
|
marked_image_file: Optional[UploadFile] = File(default=None, description="划线图片文件(JPG/PNG,≤ 1 MB)"),
|
||||||
marked_image_url: Optional[str] = Form(default=None, description="划线图片 URL"),
|
marked_image_url: Optional[str] = Form(default=None, description="划线图片 URL"),
|
||||||
marked_image_base64: Optional[str] = Form(default=None, description="划线图片 base64"),
|
marked_image_base64: Optional[str] = Form(default=None, description="划线图片 base64"),
|
||||||
original_image_file: Optional[UploadFile] = File(default=None, description="原始用户照片文件(JPG/PNG,≤ 1 MB)"),
|
|
||||||
original_image_url: Optional[str] = Form(default=None, description="原始用户照片 URL"),
|
|
||||||
original_image_base64: Optional[str] = Form(default=None, description="原始用户照片 base64"),
|
|
||||||
):
|
):
|
||||||
# 1. 两组图各三选一取图
|
# 划线图三选一取图(只需这一张)
|
||||||
marked_raw, e = await resolve_image_bytes(marked_image_file, marked_image_url, marked_image_base64)
|
marked_raw, e = await resolve_image_bytes(marked_image_file, marked_image_url, marked_image_base64)
|
||||||
if e is not None:
|
if e is not None:
|
||||||
return e
|
return e
|
||||||
orig_raw, e = await resolve_image_bytes(original_image_file, original_image_url, original_image_base64)
|
if len(marked_raw) > MAX_FILE_BYTES:
|
||||||
if e is not None:
|
|
||||||
return e
|
|
||||||
if len(marked_raw) > MAX_FILE_BYTES or len(orig_raw) > MAX_FILE_BYTES:
|
|
||||||
return err(1006, "文件超出 1 MB 限制")
|
return err(1006, "文件超出 1 MB 限制")
|
||||||
|
|
||||||
marked = cv2.imdecode(np.frombuffer(marked_raw, np.uint8), cv2.IMREAD_COLOR)
|
marked = cv2.imdecode(np.frombuffer(marked_raw, np.uint8), cv2.IMREAD_COLOR)
|
||||||
original = cv2.imdecode(np.frombuffer(orig_raw, np.uint8), cv2.IMREAD_COLOR)
|
if marked is None:
|
||||||
if marked is None or original is None:
|
|
||||||
return err(1008, "图片格式不支持(仅 JPG / PNG)")
|
return err(1008, "图片格式不支持(仅 JPG / PNG)")
|
||||||
|
|
||||||
h, w = marked.shape[:2]
|
h, w = marked.shape[:2]
|
||||||
@@ -609,7 +596,7 @@ async def hair_grow_b(
|
|||||||
from fastapi.concurrency import run_in_threadpool
|
from fastapi.concurrency import run_in_threadpool
|
||||||
from hairline.service import generate_grow_b
|
from hairline.service import generate_grow_b
|
||||||
|
|
||||||
res = await run_in_threadpool(generate_grow_b, marked, original)
|
res = await run_in_threadpool(generate_grow_b, marked)
|
||||||
if res["status"] == "no_face":
|
if res["status"] == "no_face":
|
||||||
return err(1001, "无法识别人像")
|
return err(1001, "无法识别人像")
|
||||||
if res["status"] == "no_line":
|
if res["status"] == "no_line":
|
||||||
@@ -617,7 +604,6 @@ async def hair_grow_b(
|
|||||||
|
|
||||||
grown_b64 = base64.b64encode(res["grown_png"]).decode() if res["grown_png"] else None
|
grown_b64 = base64.b64encode(res["grown_png"]).decode() if res["grown_png"] else None
|
||||||
data = {
|
data = {
|
||||||
"best_hairline_image_base64": base64.b64encode(orig_raw).decode(), # 原图原样
|
|
||||||
"hair_growth_image_base64": grown_b64,
|
"hair_growth_image_base64": grown_b64,
|
||||||
"hairline_type": "custom",
|
"hairline_type": "custom",
|
||||||
}
|
}
|
||||||
|
|||||||
+14
-16
@@ -12,12 +12,10 @@
|
|||||||
|
|
||||||
| 输入 | 说明 |
|
| 输入 | 说明 |
|
||||||
|------|------|
|
|------|------|
|
||||||
| `marked_image_*` | 已划线(医生标注发际线)的图,三选一,必填 |
|
| `marked_image_*` | 已用马克笔标注发际线的图,三选一,必填。**只需这一张**(划线图即用户照片+手绘线,不需要原图) |
|
||||||
| `original_image_*` | 原始用户照片,三选一,必填 |
|
|
||||||
|
|
||||||
| 输出 data | 决策 |
|
| 输出 data | 决策 |
|
||||||
|-----------|------|
|
|-----------|------|
|
||||||
| `best_hairline_image_url` | **= 原图 original** 原样返回(worker 返回 `best_hairline_image_base64`) |
|
|
||||||
| `hair_growth_image_url` | **生发后图片**(ComfyUI,worker 返回 `hair_growth_image_base64`) |
|
| `hair_growth_image_url` | **生发后图片**(ComfyUI,worker 返回 `hair_growth_image_base64`) |
|
||||||
| `hairline_type` | 固定 **`"custom"`**(手绘定制) |
|
| `hairline_type` | 固定 **`"custom"`**(手绘定制) |
|
||||||
|
|
||||||
@@ -46,13 +44,13 @@ detect_marker_hairline(marked_bgr, landmarks, parse_map):
|
|||||||
mediapipe/SegFormer 全崩。**必须锁 0.24.x**。
|
mediapipe/SegFormer 全崩。**必须锁 0.24.x**。
|
||||||
- 复用接口2:`forehead_upper_region` / `head_silhouette`(`hairline/mask.py`)、SegFormer / MediaPipe 单例。
|
- 复用接口2:`forehead_upper_region` / `head_silhouette`(`hairline/mask.py`)、SegFormer / MediaPipe 单例。
|
||||||
|
|
||||||
## 2. 遮罩 + 原图重画干净线
|
## 2. 遮罩(检测路径只用来建遮罩;ComfyUI 输入图 = 划线图原样)
|
||||||
|
|
||||||
- **遮罩**:path → 画成 curve_mask → 复用接口2 `_above_curve_region` + `head` + `_clean_mask`
|
- **遮罩**:path → 画成 curve_mask → 复用接口2 `mask_from_curve`(ROI ∩ 曲线以上 → 闭合)
|
||||||
得到"发际线以上闭合区域"。
|
得到"发际线以上闭合区域"。
|
||||||
- **ComfyUI 输入图**:用 **原图 original**(按需缩放到 marked 尺寸对齐坐标),**重画一条干净黑线**
|
- **ComfyUI 输入图**:直接用 **marked 划线图原样**(已含医生手绘线;`add_hair.json` 节点26
|
||||||
(检测 path 膨胀成线宽),避免医生手绘的毛刺/杂线干扰生成。
|
本来就是"画了线的照片",提示词会清除黑线再生发)。**不需要原图、不重画线**。
|
||||||
- 合成 RGBA:RGB=重画线的原图,alpha=255−mask(透明=重绘区)。复用 `compose_comfy_rgba`。
|
- 合成 RGBA:RGB=marked 划线图,alpha=255−mask(透明=重绘区)。复用 `compose_comfy_rgba`。
|
||||||
|
|
||||||
## 3. 生发(复用接口2 ComfyUI 客户端)
|
## 3. 生发(复用接口2 ComfyUI 客户端)
|
||||||
|
|
||||||
@@ -61,12 +59,11 @@ detect_marker_hairline(marked_bgr, landmarks, parse_map):
|
|||||||
## 4. worker handler(`/api/v1/hair/grow-b`)
|
## 4. worker handler(`/api/v1/hair/grow-b`)
|
||||||
|
|
||||||
```
|
```
|
||||||
1. marked + original 各三选一取图(复用 resolve_image_bytes)+ 校验(大小/解码/分辨率)
|
1. marked 三选一取图(复用 resolve_image_bytes)+ 校验(大小/解码/分辨率)。只需这一张。
|
||||||
2. 在 marked 上:landmarks(MediaPipe)+parse(SegFormer) → detect_marker_hairline
|
2. landmarks(MediaPipe)+parse(SegFormer) → detect_marker_hairline
|
||||||
- 无人脸 → 1001;未检测到画线 → 1001 "未检测到发际线划线"
|
- 无人脸 → 1001;未检测到画线 → 1001 "未检测到发际线划线"
|
||||||
3. 遮罩 + 原图重画线 → RGBA → comfyui.run → 生发图
|
3. 遮罩(mask_from_curve) + marked原样 → RGBA → comfyui.run → 生发图
|
||||||
4. return ok({ best_hairline_image_base64: 原图, hair_growth_image_base64: 生发图,
|
4. return ok({ hair_growth_image_base64: 生发图, hairline_type: "custom" })
|
||||||
hairline_type: "custom" })
|
|
||||||
异常 → 1007;重活 run_in_threadpool。
|
异常 → 1007;重活 run_in_threadpool。
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -75,8 +72,8 @@ detect_marker_hairline(marked_bgr, landmarks, parse_map):
|
|||||||
| 阶段 | 内容 | 验证 |
|
| 阶段 | 内容 | 验证 |
|
||||||
|------|------|------|
|
|------|------|------|
|
||||||
| **M1 检测** | `hairline/marker_detect.py`(黑帽+锚点+Dijkstra+拒识) | headmark test_image:检测线贴合真值;无线图被拒识 |
|
| **M1 检测** | `hairline/marker_detect.py`(黑帽+锚点+Dijkstra+拒识) | headmark test_image:检测线贴合真值;无线图被拒识 |
|
||||||
| **M2 遮罩+重画** | path→遮罩(复用) + 原图重画干净线 + RGBA 合成 | 目视:干净线在原图、遮罩贴合 |
|
| **M2 遮罩** | path→遮罩(复用 mask_from_curve) + marked原样 RGBA 合成 | 目视:遮罩贴合发际线 |
|
||||||
| **M3 接 app** | grow-b 真实实现 + 输出字段 + 1001 | curl:best=原图/grown 合法PNG/type=custom;无线→1001 |
|
| **M3 接 app** | grow-b 真实实现(仅 marked) + 输出字段 + 1001 | curl:grown 合法PNG/type=custom;无线→1001 |
|
||||||
| **M4 测试** | 检测/mask 单测 + mock-ComfyUI 集成 + 真机冒烟 | pytest 绿;真机出生发图 |
|
| **M4 测试** | 检测/mask 单测 + mock-ComfyUI 集成 + 真机冒烟 | pytest 绿;真机出生发图 |
|
||||||
|
|
||||||
## 6. 风险
|
## 6. 风险
|
||||||
@@ -84,7 +81,8 @@ detect_marker_hairline(marked_bgr, landmarks, parse_map):
|
|||||||
1. **锚点偏差/路径端点偏移**:MediaPipe 21/251 吸附后仍可能在鬓角端有偏移(实测 max~42px,少数点)。
|
1. **锚点偏差/路径端点偏移**:MediaPipe 21/251 吸附后仍可能在鬓角端有偏移(实测 max~42px,少数点)。
|
||||||
膨胀成带 + 遮罩闭合可吸收;必要时改进吸附窗口或端点截断。
|
膨胀成带 + 遮罩闭合可吸收;必要时改进吸附窗口或端点截断。
|
||||||
2. **没画线/画线极浅**:靠拒识阈值(路径平均黑帽响应)兜底,阈值需在更多真实图上标定。
|
2. **没画线/画线极浅**:靠拒识阈值(路径平均黑帽响应)兜底,阈值需在更多真实图上标定。
|
||||||
3. **marked 与 original 尺寸/对齐不一致**:按 marked 坐标系处理,original 缩放对齐;若两图非同源(不同姿态)会错位——约定二者为"同一张照片的划线版/原始版"。
|
3. **医生手绘线毛刺/杂线**:ComfyUI 输入图用 marked 原样(含手绘线),提示词会清除黑线;
|
||||||
|
检测出的干净 path 只用于建遮罩。若手绘过乱影响生成,可改为在 marked 上重画干净检测线(备选)。
|
||||||
4. **抬头纹/眉毛/发丝干扰**:黑帽 + ROI + Dijkstra 平滑已大幅抑制(调研验证抬头纹零干扰),极端情况可在代价图抑制头发区域。
|
4. **抬头纹/眉毛/发丝干扰**:黑帽 + ROI + Dijkstra 平滑已大幅抑制(调研验证抬头纹零干扰),极端情况可在代价图抑制头发区域。
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|||||||
+12
-11
@@ -230,28 +230,30 @@
|
|||||||
|
|
||||||
## 接口 3:B 端生发接口
|
## 接口 3:B 端生发接口
|
||||||
|
|
||||||
**说明**:输入医生/操作端的「划线图片」(在原图上标注了目标发际线),输出最合适的发际线图片 + 生发后图片。
|
**说明**:医生/操作端在用户照片上用马克笔标注目标发际线后,**只需上传这一张划线图**。系统检测划线 →
|
||||||
|
据此生成生发后图片。
|
||||||
|
|
||||||
**请求**:`POST /api/v1/hair/grow-b`
|
**请求**:`POST /api/v1/hair/grow-b`
|
||||||
|
|
||||||
### 输入
|
### 输入
|
||||||
|
|
||||||
划线图片同样支持「文件 / URL / base64」三种方式(字段:`marked_image_file` / `marked_image_url` / `marked_image_base64`)。
|
划线图片支持「文件 / URL / base64」三选一(字段:`marked_image_file` / `marked_image_url` / `marked_image_base64`)。
|
||||||
|
**不需要原始照片**(划线图本身就是用户照片 + 手绘线)。
|
||||||
|
|
||||||
| 参数 | 类型 | 必填 | 说明 |
|
| 参数 | 类型 | 必填 | 说明 |
|
||||||
|------|------|------|------|
|
|------|------|------|------|
|
||||||
| marked_image_* | file / string | 是 | 已划线(标注发际线)的图片,三选一 |
|
| marked_image_* | file / string | 是 | 已用马克笔标注发际线的图片,三选一 |
|
||||||
| original_image_* | file / string | 是 | 原始用户照片,需同时上传 |
|
|
||||||
|
|
||||||
### 输出(data)
|
### 输出(data)
|
||||||
|
|
||||||
| 字段 | 类型 | 说明 |
|
| 字段 | 类型 | 说明 |
|
||||||
|------|------|------|
|
|------|------|------|
|
||||||
| best_hairline_image_url | string | 最合适的发际线图片 |
|
| hair_growth_image_url | string | **生发后图片**(检测划线 → ComfyUI/Flux「植发 3 个月」效果)。worker 返回 `hair_growth_image_base64` |
|
||||||
| hair_growth_image_url | string | 生发后图片 |
|
| hairline_type | string | 发际线形,手绘为定制,固定 `"custom"` |
|
||||||
| hairline_type | string | 发际线形,需返回 |
|
|
||||||
|
|
||||||
### 响应示例(当前 Mock 返回值)
|
> 未检测到划线(或无人脸)返回 **1001**。
|
||||||
|
|
||||||
|
### 响应示例
|
||||||
|
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
@@ -259,9 +261,8 @@
|
|||||||
"message": "success",
|
"message": "success",
|
||||||
"request_id": "mock-request-id",
|
"request_id": "mock-request-id",
|
||||||
"data": {
|
"data": {
|
||||||
"best_hairline_image_url": "https://hair.xiangsilian.com/static/sample.jpg",
|
"hair_growth_image_url": "https://hair.xiangsilian.com/static/annotations/grown.png",
|
||||||
"hair_growth_image_url": "https://hair.xiangsilian.com/static/sample.jpg",
|
"hairline_type": "custom"
|
||||||
"hairline_type": "花瓣形"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|||||||
+1
-2
@@ -133,8 +133,7 @@ curl -s http://127.0.0.1:8080/gateway-health # 200
|
|||||||
| 1 | `/api/v1/face/measure` | `annotated_image_base64` | `annotated_image_url` | data 顶层 |
|
| 1 | `/api/v1/face/measure` | `annotated_image_base64` | `annotated_image_url` | data 顶层 |
|
||||||
| 2 | `/api/v1/hair/grow` | `results[].image_base64` | `results[].image_url` | **数组元素** |
|
| 2 | `/api/v1/hair/grow` | `results[].image_base64` | `results[].image_url` | **数组元素** |
|
||||||
| 2 | 〃 | `results[].grown_image_base64` | `results[].grown_image_url` | **数组元素**(可空) |
|
| 2 | 〃 | `results[].grown_image_base64` | `results[].grown_image_url` | **数组元素**(可空) |
|
||||||
| 3 | `/api/v1/hair/grow-b` | `best_hairline_image_base64` | `best_hairline_image_url` | data 顶层 |
|
| 3 | `/api/v1/hair/grow-b` | `hair_growth_image_base64` | `hair_growth_image_url` | data 顶层(可空) |
|
||||||
| 3 | 〃 | `hair_growth_image_base64` | `hair_growth_image_url` | data 顶层(可空) |
|
|
||||||
| 4 | `/api/v1/face/features` | —(无图片字段,原样透传)| — | — |
|
| 4 | `/api/v1/face/features` | —(无图片字段,原样透传)| — | — |
|
||||||
| 5 | `/api/v1/hairline/generate` | `hairline_images[].image_base64` | `hairline_images[].image_url` | **数组元素** |
|
| 5 | `/api/v1/hairline/generate` | `hairline_images[].image_base64` | `hairline_images[].image_url` | **数组元素** |
|
||||||
|
|
||||||
|
|||||||
+5
-14
@@ -193,9 +193,11 @@ def generate_hairline_pngs(image_bgr: np.ndarray, gender: str):
|
|||||||
return {"images": images, "best_center": best_center}
|
return {"images": images, "best_center": best_center}
|
||||||
|
|
||||||
|
|
||||||
def generate_grow_b(marked_bgr: np.ndarray, original_bgr: np.ndarray):
|
def generate_grow_b(marked_bgr: np.ndarray):
|
||||||
"""接口3:检测医生手绘发际线 → 遮罩 → 原图重画干净线 → ComfyUI 生发。
|
"""接口3:检测医生手绘发际线 → 遮罩 → 送 ComfyUI 生发(仅需划线图一张)。
|
||||||
|
|
||||||
|
检测路径只用来**建遮罩**;ComfyUI 输入图用 **marked 原图**(含医生手绘线,
|
||||||
|
工作流提示词会清除黑线再生发)。
|
||||||
Returns: {"grown_png": bytes 或 None, "status": "ok"|"no_face"|"no_line"}。
|
Returns: {"grown_png": bytes 或 None, "status": "ok"|"no_face"|"no_line"}。
|
||||||
"""
|
"""
|
||||||
rgb = cv2.cvtColor(marked_bgr, cv2.COLOR_BGR2RGB)
|
rgb = cv2.cvtColor(marked_bgr, cv2.COLOR_BGR2RGB)
|
||||||
@@ -208,23 +210,12 @@ def generate_grow_b(marked_bgr: np.ndarray, original_bgr: np.ndarray):
|
|||||||
return {"grown_png": None, "status": "no_line"}
|
return {"grown_png": None, "status": "no_line"}
|
||||||
|
|
||||||
h, w = marked_bgr.shape[:2]
|
h, w = marked_bgr.shape[:2]
|
||||||
# 原图对齐到 marked 坐标系(同一张照片的原始版/划线版,尺寸应一致)
|
|
||||||
orig = original_bgr
|
|
||||||
if orig.shape[:2] != (h, w):
|
|
||||||
orig = cv2.resize(orig, (w, h), interpolation=cv2.INTER_AREA)
|
|
||||||
|
|
||||||
# 在原图上重画干净黑线(膨胀成笔迹宽度),替代医生手绘的毛刺
|
|
||||||
line_w = max(2, int(w * 0.006))
|
line_w = max(2, int(w * 0.006))
|
||||||
marked_clean = orig.copy()
|
|
||||||
cv2.polylines(marked_clean, [path[:, ::-1].reshape(-1, 1, 2)], False,
|
|
||||||
(0, 0, 0), line_w, lineType=cv2.LINE_AA)
|
|
||||||
|
|
||||||
# 遮罩:检测路径曲线 + ROI 闭合
|
|
||||||
curve_mask = path_to_curve_mask(path, h, w, thickness=max(3, line_w))
|
curve_mask = path_to_curve_mask(path, h, w, thickness=max(3, line_w))
|
||||||
mask = mask_from_curve(curve_mask, landmarks, parse_map)
|
mask = mask_from_curve(curve_mask, landmarks, parse_map)
|
||||||
|
|
||||||
buf = io.BytesIO()
|
buf = io.BytesIO()
|
||||||
compose_comfy_rgba(marked_clean, mask).save(buf, format="PNG")
|
compose_comfy_rgba(marked_bgr, mask).save(buf, format="PNG") # marked 原图 + 遮罩
|
||||||
grown_png = comfyui.run(buf.getvalue())
|
grown_png = comfyui.run(buf.getvalue())
|
||||||
return {"grown_png": grown_png, "status": "ok"}
|
return {"grown_png": grown_png, "status": "ok"}
|
||||||
|
|
||||||
|
|||||||
+7
-13
@@ -106,33 +106,27 @@ def test_grow_female_returns_5(client, monkeypatch):
|
|||||||
GROWB = "/api/v1/hair/grow-b"
|
GROWB = "/api/v1/hair/grow-b"
|
||||||
|
|
||||||
|
|
||||||
def test_growb_missing_original_1007(client):
|
def test_growb_missing_marked_1007(client):
|
||||||
files = {"marked_image_file": ("m.jpg", open(fixture("marked_hairline.jpg"), "rb"), "application/octet-stream")}
|
r = client.post(GROWB, headers=H) # 一张图都没传
|
||||||
r = client.post(GROWB, headers=H, files=files)
|
|
||||||
assert r.json()["code"] == 1007
|
assert r.json()["code"] == 1007
|
||||||
|
|
||||||
|
|
||||||
def test_growb_no_line_1001(client):
|
def test_growb_no_line_1001(client):
|
||||||
f = lambda: open(fixture("frontal.jpg"), "rb")
|
files = {"marked_image_file": ("m.jpg", open(fixture("frontal.jpg"), "rb"), "application/octet-stream")}
|
||||||
files = {"marked_image_file": ("m.jpg", f(), "application/octet-stream"),
|
r = client.post(GROWB, headers=H, files=files) # 无划线 → 拒识
|
||||||
"original_image_file": ("o.jpg", f(), "application/octet-stream")}
|
|
||||||
r = client.post(GROWB, headers=H, files=files)
|
|
||||||
assert r.json()["code"] == 1001
|
assert r.json()["code"] == 1001
|
||||||
|
|
||||||
|
|
||||||
def test_growb_success(client, monkeypatch):
|
def test_growb_success(client, monkeypatch):
|
||||||
import hairline.comfyui as comfy
|
import hairline.comfyui as comfy
|
||||||
monkeypatch.setattr(comfy, "run", lambda *a, **k: _PNG_1x1)
|
monkeypatch.setattr(comfy, "run", lambda *a, **k: _PNG_1x1)
|
||||||
fm = open(fixture("marked_hairline.jpg"), "rb")
|
files = {"marked_image_file": ("m.jpg", open(fixture("marked_hairline.jpg"), "rb"), "application/octet-stream")}
|
||||||
fo = open(fixture("marked_hairline.jpg"), "rb")
|
body = client.post(GROWB, headers=H, files=files).json() # 只传划线图一张
|
||||||
files = {"marked_image_file": ("m.jpg", fm, "application/octet-stream"),
|
|
||||||
"original_image_file": ("o.jpg", fo, "application/octet-stream")}
|
|
||||||
body = client.post(GROWB, headers=H, files=files).json()
|
|
||||||
assert body["code"] == 0, body
|
assert body["code"] == 0, body
|
||||||
d = body["data"]
|
d = body["data"]
|
||||||
assert d["hairline_type"] == "custom"
|
assert d["hairline_type"] == "custom"
|
||||||
assert base64.b64decode(d["hair_growth_image_base64"])[:8] == b"\x89PNG\r\n\x1a\n"
|
assert base64.b64decode(d["hair_growth_image_base64"])[:8] == b"\x89PNG\r\n\x1a\n"
|
||||||
assert d["best_hairline_image_base64"] # 原图原样(非空)
|
assert "best_hairline_image_base64" not in d # 已去掉该字段
|
||||||
assert "best_hairline_image_url" not in d
|
assert "best_hairline_image_url" not in d
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user