commit 356a308f9b4223da15269d254c21609f2daa3025 Author: SkyLi Date: Sat Mar 28 20:28:32 2026 +0800 Initial commit: Infrared Equipment Fault Detection HTTP Service - Add FastAPI microservice for infrared equipment fault detection - Add dual pyproject.toml: local development and Docker deployment - Add MANUAL_SETUP.md for manual file placement instructions - Add Dockerfile for containerized deployment - Add sample .env.example for PROJECT_ROOT configuration - Configure gitignore for weights, lib, venv, and environment files diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..f01d8fe --- /dev/null +++ b/.env.example @@ -0,0 +1,2 @@ +# 项目根目录(修改为你的实际路径) +PROJECT_ROOT=/path/to/Infrared-ai-inference-service diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..cd2bc25 --- /dev/null +++ b/.gitignore @@ -0,0 +1,53 @@ +__pycache__/ +*.py[cod] +*.egg-info/ +dist/ +build/ +*.egg +.pytest_cache/ +.ruff_cache/ +.mypy_cache/ +.tox/ +cov.xml +*.cover +*.log + +# Model weights +weights/ +*.pt +*.pth +*.onnx +*.ckpt + +# Lib (manually placed) +lib/ + +# Environment +.python-version +.venv/ +venv/ +.env +.env.local + +# Lock files +uv.lock + +# IDE +.vscode/ +.idea/ +*.swp +*.swo + +# OS +.DS_Store +Thumbs.db + +# Docker +.dockerignore + +# Claude +.claude/ +CLAUDE.md + +# Tests +tests/ \ No newline at end of file diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..a0d380d --- /dev/null +++ b/Dockerfile @@ -0,0 +1,25 @@ +FROM python:3.10-slim + +WORKDIR /app + +# 安装 uv +RUN pip install uv + +# 复制本地 whl 文件(预下载) +COPY lib/ /app/lib/ + +# 复制依赖配置(Docker 专用) +COPY pyproject.docker.toml /app/pyproject.toml + +# 安装依赖(包括 infrared-data 从本地 whl) +RUN uv pip install --system -r /app/pyproject.toml + +# 复制源码 +COPY src/ ./src/ + +# 复制预下载的权重文件 +COPY weights/ /app/weights/ + +EXPOSE 8000 + +CMD ["python", "-m", "uvicorn", "src.main:app", "--host", "0.0.0.0", "--port", "8000"] diff --git a/MANUAL_SETUP.md b/MANUAL_SETUP.md new file mode 100644 index 0000000..148d095 --- /dev/null +++ b/MANUAL_SETUP.md @@ -0,0 +1,58 @@ +# 手动部署说明 + +由于网络限制,权重文件和 whl 包需要手动放置到本地目录。 + +## 必需文件 + +### 1. whl 包 +- **文件名**: `infrared_data-0.1.0-py3-none-any.whl` +- **存放路径**: `lib/infrared_data-0.1.0-py3-none-any.whl` +- **下载地址**: 联系项目维护者获取 + +### 2. 模型权重文件 +- **文件名**: `best.pt` +- **存放路径**: `weights/best.pt` +- **下载地址**: 联系项目维护者获取 + +## 目录结构 + +``` +. +├── lib/ +│ └── infrared_data-0.1.0-py3-none-any.whl +├── weights/ +│ └── best.pt +├── src/ +│ ├── __init__.py +│ ├── config.py +│ └── main.py +├── tests/ +├── pyproject.toml +└── Dockerfile +``` + +## 安装步骤 + +```bash +# 1. 放置上述文件到对应目录 + +# 2. 创建 .env 文件(包含项目路径) +# 注意:此文件包含绝对路径,每个开发者需要根据实际路径修改 +cp .env.example .env # 或手动创建 + +# 3. 创建虚拟环境并安装依赖 +set -a && source .env && uv sync + +# 4. 启动服务 +set -a && source .env && INFERENCE_DEVICE=cpu uv run uvicorn src.main:app --host 0.0.0.0 --port 8000 +``` + +## 验证 + +```bash +# 健康检查 +curl http://localhost:8000/health + +# 测试推理 +curl -X POST http://localhost:8000/infer -F "image=@your_image.jpg" +``` diff --git a/README.md b/README.md new file mode 100644 index 0000000..0007ff8 --- /dev/null +++ b/README.md @@ -0,0 +1,148 @@ +# Infrared Equipment Fault Detection Service + +红外设备故障检测 HTTP 微服务,基于 `infrared-data` whl 包提供推理接口。 + +## 功能特性 + +- RESTful API 服务 +- 支持红外图像故障诊断 +- 返回检测框、温度特征、故障状态 +- CPU/GPU 灵活切换 + +## 项目结构 + +``` +. +├── lib/ # whl 包(需手动放置) +│ └── infrared_data-0.1.0-py3-none-any.whl +├── weights/ # 模型权重(需手动放置) +│ └── best.pt +├── src/ +│ ├── __init__.py +│ ├── config.py # 配置 +│ └── main.py # FastAPI 应用 +├── tests/ +│ └── valid/ # 测试图片和结果 +├── pyproject.toml # 本地依赖配置 +├── pyproject.docker.toml # Docker 依赖配置 +├── Dockerfile # Docker 镜像 +├── MANUAL_SETUP.md # 手动部署说明 +└── README.md # 本文件 +``` + +## 快速开始 + +### 1. 手动放置文件 + +在部署前,需要手动获取并放置以下文件: + +| 文件 | 存放路径 | +|------|----------| +| whl 包 | `lib/infrared_data-0.1.0-py3-none-any.whl` | +| 模型权重 | `weights/best.pt` | + +详细说明请参考 [MANUAL_SETUP.md](MANUAL_SETUP.md)。 + +### 2. 安装依赖 + +```bash +uv sync +``` + +### 3. 启动服务 + +```bash +# CPU 模式 +INFERENCE_DEVICE=cpu uv run uvicorn src.main:app --host 0.0.0.0 --port 8000 + +# GPU 模式 +INFERENCE_DEVICE=cuda:0 uv run uvicorn src.main:app --host 0.0.0.0 --port 8000 +``` + +### 4. 测试 + +```bash +# 健康检查 +curl http://localhost:8000/health + +# 推理测试 +curl -X POST http://localhost:8000/infer -F "image=@your_image.jpg" +``` + +## API 文档 + +启动服务后访问: http://localhost:8000/docs + +### POST /infer + +上传红外图像进行故障诊断。 + +**请求** +- Content-Type: `multipart/form-data` +- 字段: `image` (图片文件) + +**响应** +```json +{ + "image": "array", + "detections": [ + { + "bbox": [x1, y1, x2, y2], + "bbox_xywh": [cx, cy, w, h], + "confidence": 0.94, + "class_id": 4, + "class_name": "conservator" + } + ], + "faults": [ + { + "temp_max": 255.0, + "temp_min": 0.0, + "temp_mean": 81.1, + "temp_std": 68.3, + "temp_median": 57.0, + "fault_status": "abnormal", + "fault_type": "hotspot" + } + ], + "summary": { + "status": "fault_detected", + "detection_count": 3, + "abnormal_count": 3, + "fault_types": ["hotspot"] + } +} +``` + +### GET /health + +健康检查接口。 + +**响应** +```json +{ + "status": "ok", + "device": "cpu" +} +``` + +## Docker 部署 + +```bash +# 构建镜像 +docker build -t infrared-inference . + +# 运行容器 +docker run -p 8000:8000 infrared-inference +``` + +## 故障类型 + +| 类型 | 说明 | +|------|------| +| `none` | 正常 | +| `hotspot` | 热点异常 | +| `local_overheating` | 局部过热 | +| `overheat` | 过热 | +| `suspect` | 可疑 | + diff --git a/pyproject.docker.toml b/pyproject.docker.toml new file mode 100644 index 0000000..12b06fd --- /dev/null +++ b/pyproject.docker.toml @@ -0,0 +1,12 @@ +[project] +name = "infrared-inference" +version = "0.1.0" +description = "HTTP microservice for infrared equipment fault detection" +requires-python = ">=3.10" +dependencies = [ + "fastapi>=0.115.0", + "uvicorn[standard]>=0.30.0", + "python-multipart>=0.0.12", + "aiofiles>=24.0.0", + "infrared-data @ file:///app/lib/infrared_data-0.1.0-py3-none-any.whl", +] diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..1ba3df4 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,12 @@ +[project] +name = "infrared-inference" +version = "0.1.0" +description = "HTTP microservice for infrared equipment fault detection" +requires-python = ">=3.10" +dependencies = [ + "fastapi>=0.115.0", + "uvicorn[standard]>=0.30.0", + "python-multipart>=0.0.12", + "aiofiles>=24.0.0", + "infrared-data @ file://${PROJECT_ROOT}/lib/infrared_data-0.1.0-py3-none-any.whl", +] diff --git a/src/__init__.py b/src/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/config.py b/src/config.py new file mode 100644 index 0000000..82cf909 --- /dev/null +++ b/src/config.py @@ -0,0 +1,19 @@ +import os +from pathlib import Path + +# 权重文件地址 +WEIGHTS_URL = "http://193.112.94.100:9000/SkyLiCoder/Infrared-ai-research-lab/releases/download/v0.1.0/best.pt" + +# 权重本地路径 +WEIGHTS_DIR = Path(__file__).parent.parent / "weights" +WEIGHTS_PATH = WEIGHTS_DIR / "best.pt" + +# whl 文件地址 +WHL_URL = "http://193.112.94.100:9000/SkyLiCoder/Infrared-ai-research-lab/releases/download/v0.1.0/infrared_data-0.1.0-py3-none-any.whl" + +# whl 本地路径 +WHL_DIR = Path(__file__).parent.parent / "lib" +WHL_PATH = WHL_DIR / "infrared_data-0.1.0-py3-none-any.whl" + +# 推理设备: "cuda:0" / "cpu" +DEVICE = os.getenv("INFERENCE_DEVICE", "cpu") diff --git a/src/main.py b/src/main.py new file mode 100644 index 0000000..35003a6 --- /dev/null +++ b/src/main.py @@ -0,0 +1,66 @@ +import os +import io +from pathlib import Path +from typing import Optional + +import numpy as np +from fastapi import FastAPI, UploadFile, File, HTTPException +from PIL import Image +import uvicorn + +from infrared_data import FaultDiagnoser +from src.config import WEIGHTS_PATH, DEVICE + +app = FastAPI(title="Infrared Equipment Fault Detection API") + +# 全局诊断器实例 +_diagnoser: Optional[FaultDiagnoser] = None + + +def get_diagnoser() -> FaultDiagnoser: + global _diagnoser + if _diagnoser is None: + if not WEIGHTS_PATH.exists(): + raise RuntimeError( + f"Weight file not found at {WEIGHTS_PATH}. " + "Please run: python scripts/download_assets.py" + ) + _diagnoser = FaultDiagnoser(str(WEIGHTS_PATH)) + return _diagnoser + + +@app.post("/infer") +async def infer(image: UploadFile = File(...)) -> dict: + """ + 接收一张红外图片,返回故障诊断结果(JSON)。 + + 返回结构: + { + "image": str, + "detections": [...], + "faults": [...], + "summary": { + "status": str, + "detection_count": int, + "abnormal_count": int, + "fault_types": [...] + } + } + """ + # 读取图片内容 + contents = await image.read() + img = Image.open(io.BytesIO(contents)).convert("RGB") + img_array = np.array(img) + + diagnoser = get_diagnoser() + result = diagnoser.diagnose(img_array, device=DEVICE) + return result + + +@app.get("/health") +async def health() -> dict: + return {"status": "ok", "device": DEVICE} + + +if __name__ == "__main__": + uvicorn.run(app, host="0.0.0.0", port=8000)