client.py 9.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285
  1. #!/usr/bin/env python3
  2. # -*- coding: utf-8 -*-
  3. """
  4. 入户门检测服务客户端
  5. 用于调用 FastAPI 服务器进行入户门位置检测
  6. """
  7. import os
  8. import sys
  9. import time
  10. import json
  11. import argparse
  12. from pathlib import Path
  13. from typing import Optional, Dict, Any, List
  14. import requests
  15. from tqdm import tqdm
  16. class EntranceDoorClient:
  17. """入户门检测客户端"""
  18. def __init__(self, base_url: str = "http://localhost:8000"):
  19. self.base_url = base_url.rstrip("/")
  20. self.session = requests.Session()
  21. def health_check(self) -> Dict[str, Any]:
  22. """健康检查"""
  23. response = self.session.get(f"{self.base_url}/health")
  24. response.raise_for_status()
  25. return response.json()
  26. def detect(
  27. self,
  28. scene_folder: str,
  29. model_path: Optional[str] = None,
  30. conf: float = 0.35,
  31. iou: float = 0.45,
  32. voxel_size: float = 0.03,
  33. imgsz: Optional[List[int]] = None,
  34. vis_ply: bool = False,
  35. poll_interval: float = 2.0,
  36. timeout: int = 600
  37. ) -> Dict[str, Any]:
  38. """
  39. 提交检测任务并等待结果
  40. Args:
  41. scene_folder: 场景文件夹路径
  42. model_path: YOLOE 模型路径
  43. conf: 检测置信度阈值
  44. iou: NMS IoU 阈值
  45. voxel_size: 点云体素尺寸
  46. imgsz: YOLOE 输入图像尺寸 [高,宽]
  47. vis_ply: 是否生成可视化 PLY
  48. poll_interval: 查询状态间隔(秒)
  49. timeout: 超时时间(秒)
  50. Returns:
  51. 检测结果
  52. """
  53. # 提交任务
  54. request_data = {
  55. "scene_folder": scene_folder,
  56. "conf": conf,
  57. "iou": iou,
  58. "voxel_size": voxel_size,
  59. "vis_ply": vis_ply
  60. }
  61. if model_path:
  62. request_data["model_path"] = model_path
  63. if imgsz:
  64. request_data["imgsz"] = imgsz
  65. response = self.session.post(f"{self.base_url}/detect", json=request_data)
  66. response.raise_for_status()
  67. submit_result = response.json()
  68. task_id = submit_result["task_id"]
  69. print(f"任务已提交:{task_id}")
  70. print(f" 场景:{scene_folder}")
  71. print(f" 可视化:{'是' if vis_ply else '否'}")
  72. # 轮询任务状态
  73. start_time = time.time()
  74. print("\n等待检测结果...")
  75. with tqdm(total=100, desc="处理进度") as pbar:
  76. while True:
  77. # 检查超时
  78. if time.time() - start_time > timeout:
  79. raise TimeoutError(f"任务 {task_id} 超时")
  80. # 查询状态
  81. status_response = self.session.get(f"{self.base_url}/status/{task_id}")
  82. status_response.raise_for_status()
  83. status = status_response.json()
  84. # 更新进度条
  85. progress = status.get("progress", 0) or 0
  86. pbar.n = int(progress * 100)
  87. pbar.set_description(f"状态:{status['status']}")
  88. pbar.refresh()
  89. # 检查任务状态
  90. if status["status"] == "completed":
  91. print(f"\n✓ 检测完成!")
  92. return status
  93. elif status["status"] == "failed":
  94. print(f"\n✗ 检测失败:{status.get('error', '未知错误')}")
  95. raise RuntimeError(f"任务失败:{status.get('error', '未知错误')}")
  96. # 等待后重试
  97. time.sleep(poll_interval)
  98. def detect_and_save(
  99. self,
  100. scene_folder: str,
  101. output_dir: Optional[str] = None, # 此参数已废弃
  102. **kwargs
  103. ) -> Dict[str, Any]:
  104. """
  105. 检测并保存结果到本地
  106. Args:
  107. scene_folder: 场景文件夹路径
  108. output_dir: 已废弃,结果始终保存到 scene_folder/output
  109. **kwargs: 传递给 detect() 的参数
  110. Returns:
  111. 检测结果
  112. """
  113. # 执行检测
  114. result = self.detect(scene_folder, **kwargs)
  115. # 结果已保存在场景文件夹的 output 目录下(由服务器端生成)
  116. output_path = Path(scene_folder) / "output"
  117. print(f"\n结果已保存在:{output_path}/")
  118. if kwargs.get("vis_ply", False):
  119. print(f" - entrance_position.json (JSON 结果)")
  120. print(f" - vis.ply (可视化文件)")
  121. else:
  122. print(f" - entrance_position.json (JSON 结果)")
  123. return result["result"]
  124. def get_status(self, task_id: str) -> Dict[str, Any]:
  125. """查询任务状态"""
  126. response = self.session.get(f"{self.base_url}/status/{task_id}")
  127. response.raise_for_status()
  128. return response.json()
  129. def get_result(self, task_id: str) -> Dict[str, Any]:
  130. """获取检测结果"""
  131. response = self.session.get(f"{self.base_url}/result/{task_id}")
  132. response.raise_for_status()
  133. return response.json()
  134. def download_ply(self, task_id: str, output_path: str) -> str:
  135. """下载可视化 PLY 文件"""
  136. response = self.session.get(f"{self.base_url}/result/{task_id}/ply")
  137. response.raise_for_status()
  138. with open(output_path, "wb") as f:
  139. f.write(response.content)
  140. return output_path
  141. def delete_task(self, task_id: str) -> bool:
  142. """删除任务"""
  143. response = self.session.delete(f"{self.base_url}/task/{task_id}")
  144. response.raise_for_status()
  145. return True
  146. def main():
  147. parser = argparse.ArgumentParser(
  148. description="入户门检测客户端",
  149. formatter_class=argparse.RawDescriptionHelpFormatter,
  150. epilog="""
  151. 示例:
  152. # 健康检查
  153. python client.py --health
  154. # 检测单个场景
  155. python client.py -s scene0001
  156. # 带可视化输出
  157. python client.py -s scene0001 --vis_ply
  158. # 指定服务器地址
  159. python client.py -s scene0001 --server http://192.168.1.100:8000
  160. # 调整检测参数
  161. python client.py -s scene0001 --conf 0.4 --iou 0.5
  162. """
  163. )
  164. parser.add_argument("--server", type=str, default="http://localhost:8000",
  165. help="服务器地址")
  166. parser.add_argument("--scene", "-s", type=str, required=True,
  167. help="场景文件夹")
  168. parser.add_argument("--output", "-o", type=str, default="client_results",
  169. help="输出目录")
  170. parser.add_argument("--model", "-m", type=str, default="yoloe-26x-seg.pt",
  171. help="YOLOE 模型路径")
  172. parser.add_argument("--conf", type=float, default=0.35,
  173. help="置信度阈值")
  174. parser.add_argument("--iou", type=float, default=0.45,
  175. help="NMS IoU 阈值")
  176. parser.add_argument("--voxel-size", type=float, default=0.03,
  177. help="点云体素大小")
  178. parser.add_argument("--imgsz", type=int, nargs=2, default=[1024, 2048],
  179. metavar=("HEIGHT", "WIDTH"),
  180. help="YOLOE 输入图像尺寸 (高度 宽度)")
  181. parser.add_argument("--vis_ply", action="store_true",
  182. help="是否生成可视化 PLY")
  183. parser.add_argument("--health", action="store_true",
  184. help="执行健康检查并退出")
  185. parser.add_argument("--timeout", type=int, default=600,
  186. help="超时时间(秒)")
  187. args = parser.parse_args()
  188. # 创建客户端
  189. client = EntranceDoorClient(args.server)
  190. # 健康检查
  191. if args.health:
  192. try:
  193. health = client.health_check()
  194. print("服务器健康状态:")
  195. print(f" 状态:{health['status']}")
  196. print(f" 模型已加载:{health['model_loaded']}")
  197. print(f" GPU 可用:{health['gpu_available']}")
  198. print(f" 时间:{health['timestamp']}")
  199. except requests.exceptions.ConnectionError:
  200. print(f"无法连接到服务器:{args.server}")
  201. sys.exit(1)
  202. return
  203. # 检测场景
  204. try:
  205. result = client.detect_and_save(
  206. scene_folder=args.scene,
  207. output_dir=args.output,
  208. model_path=args.model,
  209. conf=args.conf,
  210. iou=args.iou,
  211. voxel_size=args.voxel_size,
  212. imgsz=args.imgsz,
  213. vis_ply=args.vis_ply,
  214. timeout=args.timeout
  215. )
  216. print("\n" + "=" * 50)
  217. print("检测结果")
  218. print("=" * 50)
  219. if result.get("entrance_position"):
  220. pos = result["entrance_position"]
  221. print(f"入户门位置:({pos['x']:.3f}, {pos['y']:.3f}, {pos['z']:.3f})")
  222. print(f"来源:{result.get('source', 'unknown')}")
  223. print(f"是否估计:{result.get('is_estimated', False)}")
  224. if result.get("door_info"):
  225. door = result["door_info"]
  226. print(f"\n门信息:")
  227. print(f" 置信度:{door['confidence']:.3f}")
  228. dim = door['dimensions']
  229. print(f" 尺寸:{dim['width']:.2f}m × {dim['height']:.2f}m × {dim['thickness']:.3f}m")
  230. print("\n✓ 处理完成")
  231. except Exception as e:
  232. print(f"\n✗ 错误:{e}")
  233. sys.exit(1)
  234. if __name__ == "__main__":
  235. main()