DatsuNTOYOTA commited on
Commit
8ef0522
·
verified ·
1 Parent(s): 383097d

Update app/backend/main.py

Browse files
Files changed (1) hide show
  1. app/backend/main.py +202 -201
app/backend/main.py CHANGED
@@ -1,202 +1,203 @@
1
- from __future__ import annotations
2
-
3
- import json
4
- import os
5
- import shutil
6
- import subprocess
7
- import sys
8
- import tempfile
9
- from pathlib import Path
10
- from typing import Any, Dict
11
-
12
- from fastapi import FastAPI, File, HTTPException, UploadFile
13
- from fastapi.middleware.cors import CORSMiddleware
14
- from fastapi.responses import JSONResponse
15
- from PIL import Image
16
-
17
- # ==========
18
- # Настройки
19
- # ==========
20
-
21
-
22
- PROJECT_ROOT = Path(
23
- os.getenv("POSTER_PROJECT_ROOT", "/Practise/app")
24
- ).resolve()
25
- SRC_DIR = PROJECT_ROOT / "src"
26
-
27
- if str(SRC_DIR) not in sys.path:
28
- sys.path.append(str(SRC_DIR))
29
-
30
-
31
- # =========================
32
- # Инференс через существующий script
33
- # =========================
34
-
35
- def extract_json_from_stdout(stdout: str) -> Dict[str, Any]:
36
- """
37
- Некоторые библиотеки и модели печатают служебные логи до JSON.
38
- Ищем первый валидный JSON-объект.
39
- """
40
- stdout = safe_text(stdout).strip()
41
- if not stdout:
42
- raise RuntimeError("Empty stdout: inference script returned no JSON output.")
43
-
44
- start_idx = stdout.find("{")
45
- if start_idx == -1:
46
- raise RuntimeError(f"JSON not found in stdout:\n{stdout}")
47
-
48
- candidate = stdout[start_idx:]
49
-
50
- try:
51
- return json.loads(candidate)
52
- except json.JSONDecodeError:
53
- end_idx = candidate.rfind("}")
54
- if end_idx == -1:
55
- raise RuntimeError(f"JSON closing brace not found:\n{stdout}")
56
- candidate = candidate[: end_idx + 1]
57
- return json.loads(candidate)
58
-
59
- def safe_text(value: Any) -> str:
60
- if value is None:
61
- return ""
62
- return str(value)
63
-
64
- def run_model_inference(image_path: Path) -> Dict[str, Any]:
65
- script_path = PROJECT_ROOT / "src" / "predict_one_image_with_metrics.py"
66
-
67
- if not script_path.exists():
68
- raise RuntimeError(
69
- f"Inference script not found: {script_path}\n"
70
- f"PROJECT_ROOT={PROJECT_ROOT}"
71
- )
72
-
73
- cmd = [
74
- sys.executable,
75
- str(script_path),
76
- str(image_path),
77
- "--json",
78
- ]
79
-
80
- env = os.environ.copy()
81
- env["PYTHONIOENCODING"] = "utf-8"
82
-
83
- result = subprocess.run(
84
- cmd,
85
- capture_output=True,
86
- text=True,
87
- encoding="utf-8",
88
- cwd=str(PROJECT_ROOT),
89
- env=env,
90
- )
91
-
92
- stdout_text = safe_text(result.stdout).strip()
93
- stderr_text = safe_text(result.stderr).strip()
94
-
95
- if result.returncode != 0:
96
- raise RuntimeError(
97
- "Inference failed.\n"
98
- f"CMD: {' '.join(cmd)}\n"
99
- f"CWD: {PROJECT_ROOT}\n"
100
- f"RETURN CODE: {result.returncode}\n"
101
- f"STDOUT:\n{stdout_text}\n"
102
- f"STDERR:\n{stderr_text}"
103
- )
104
-
105
- if not stdout_text:
106
- raise RuntimeError(
107
- "Inference returned success code but empty stdout.\n"
108
- f"CMD: {' '.join(cmd)}\n"
109
- f"CWD: {PROJECT_ROOT}\n"
110
- f"RETURN CODE: {result.returncode}\n"
111
- f"STDERR:\n{stderr_text}"
112
- )
113
-
114
- data = extract_json_from_stdout(stdout_text)
115
-
116
- if not isinstance(data, dict):
117
- raise RuntimeError("Inference returned non-dict JSON.")
118
-
119
- if "ai_detection" not in data:
120
- data["ai_detection"] = None
121
-
122
- return data
123
-
124
-
125
- # ==========
126
- # FastAPI
127
- # ==========
128
-
129
- app = FastAPI(
130
- title="Poster Design Analyzer API",
131
- version="1.0.0",
132
- description="API для анализа постеров и возврата 5 оценок и диагностического анализа."
133
- )
134
-
135
- app.add_middleware(
136
- CORSMiddleware,
137
- allow_origins=["*"],
138
- allow_credentials=True,
139
- allow_methods=["*"],
140
- allow_headers=["*"],
141
- )
142
-
143
-
144
- @app.get("/")
145
- def root() -> Dict[str, Any]:
146
- return {
147
- "service": "Poster Design Analyzer API",
148
- "status": "ok",
149
- "docs": "/docs",
150
- "health": "/health",
151
- "analyze": "/analyze",
152
- }
153
-
154
-
155
- @app.get("/health")
156
- def health() -> Dict[str, str]:
157
- return {"status": "ok"}
158
-
159
-
160
- @app.post("/analyze")
161
- async def analyze(file: UploadFile = File(...)) -> JSONResponse:
162
- if not file.filename:
163
- raise HTTPException(status_code=400, detail="Файл не передан.")
164
-
165
- suffix = Path(file.filename).suffix.lower()
166
- if suffix not in {".jpg", ".jpeg", ".png", ".webp", ".bmp"}:
167
- raise HTTPException(status_code=400, detail="Неподдерживаемый формат файла.")
168
-
169
- temp_dir = Path(tempfile.mkdtemp(prefix="poster_analyze_"))
170
- temp_path = temp_dir / f"input{suffix}"
171
-
172
- try:
173
- contents = await file.read()
174
- if not contents:
175
- raise HTTPException(status_code=400, detail="Пустой файл.")
176
-
177
- temp_path.write_bytes(contents)
178
-
179
- # Валидация изображения
180
- try:
181
- with Image.open(temp_path) as img:
182
- img.verify()
183
- except Exception as e:
184
- raise HTTPException(
185
- status_code=400,
186
- detail=f"Файл не является корректным изображением: {e}"
187
- )
188
-
189
- inference_result = run_model_inference(temp_path)
190
-
191
- response = {
192
- "filename": file.filename,
193
- **inference_result,
194
- }
195
- return JSONResponse(content=response)
196
-
197
- except HTTPException:
198
- raise
199
- except Exception as e:
200
- raise HTTPException(status_code=500, detail=f"Ошибка инференса: {e}")
201
- finally:
 
202
  shutil.rmtree(temp_dir, ignore_errors=True)
 
1
+ from __future__ import annotations
2
+
3
+ import json
4
+ import os
5
+ import shutil
6
+ import subprocess
7
+ import sys
8
+ import tempfile
9
+ from pathlib import Path
10
+ from typing import Any, Dict
11
+
12
+ from fastapi import FastAPI, File, HTTPException, UploadFile
13
+ from fastapi.middleware.cors import CORSMiddleware
14
+ from fastapi.responses import JSONResponse
15
+ from PIL import Image
16
+
17
+ # ==========
18
+ # Настройки
19
+ # ==========
20
+
21
+
22
+ PROJECT_ROOT = Path(
23
+ os.getenv("POSTER_PROJECT_ROOT", str(Path(__file__).resolve().parents[1]))
24
+ ).resolve()
25
+
26
+ SRC_DIR = PROJECT_ROOT / "src"
27
+
28
+ if str(SRC_DIR) not in sys.path:
29
+ sys.path.append(str(SRC_DIR))
30
+
31
+
32
+ # =========================
33
+ # Инференс через существующий script
34
+ # =========================
35
+
36
+ def extract_json_from_stdout(stdout: str) -> Dict[str, Any]:
37
+ """
38
+ Некоторые библиотеки и модели печатают служебные логи до JSON.
39
+ Ищем первый валидный JSON-объект.
40
+ """
41
+ stdout = safe_text(stdout).strip()
42
+ if not stdout:
43
+ raise RuntimeError("Empty stdout: inference script returned no JSON output.")
44
+
45
+ start_idx = stdout.find("{")
46
+ if start_idx == -1:
47
+ raise RuntimeError(f"JSON not found in stdout:\n{stdout}")
48
+
49
+ candidate = stdout[start_idx:]
50
+
51
+ try:
52
+ return json.loads(candidate)
53
+ except json.JSONDecodeError:
54
+ end_idx = candidate.rfind("}")
55
+ if end_idx == -1:
56
+ raise RuntimeError(f"JSON closing brace not found:\n{stdout}")
57
+ candidate = candidate[: end_idx + 1]
58
+ return json.loads(candidate)
59
+
60
+ def safe_text(value: Any) -> str:
61
+ if value is None:
62
+ return ""
63
+ return str(value)
64
+
65
+ def run_model_inference(image_path: Path) -> Dict[str, Any]:
66
+ script_path = PROJECT_ROOT / "src" / "predict_one_image_with_metrics.py"
67
+
68
+ if not script_path.exists():
69
+ raise RuntimeError(
70
+ f"Inference script not found: {script_path}\n"
71
+ f"PROJECT_ROOT={PROJECT_ROOT}"
72
+ )
73
+
74
+ cmd = [
75
+ sys.executable,
76
+ str(script_path),
77
+ str(image_path),
78
+ "--json",
79
+ ]
80
+
81
+ env = os.environ.copy()
82
+ env["PYTHONIOENCODING"] = "utf-8"
83
+
84
+ result = subprocess.run(
85
+ cmd,
86
+ capture_output=True,
87
+ text=True,
88
+ encoding="utf-8",
89
+ cwd=str(PROJECT_ROOT),
90
+ env=env,
91
+ )
92
+
93
+ stdout_text = safe_text(result.stdout).strip()
94
+ stderr_text = safe_text(result.stderr).strip()
95
+
96
+ if result.returncode != 0:
97
+ raise RuntimeError(
98
+ "Inference failed.\n"
99
+ f"CMD: {' '.join(cmd)}\n"
100
+ f"CWD: {PROJECT_ROOT}\n"
101
+ f"RETURN CODE: {result.returncode}\n"
102
+ f"STDOUT:\n{stdout_text}\n"
103
+ f"STDERR:\n{stderr_text}"
104
+ )
105
+
106
+ if not stdout_text:
107
+ raise RuntimeError(
108
+ "Inference returned success code but empty stdout.\n"
109
+ f"CMD: {' '.join(cmd)}\n"
110
+ f"CWD: {PROJECT_ROOT}\n"
111
+ f"RETURN CODE: {result.returncode}\n"
112
+ f"STDERR:\n{stderr_text}"
113
+ )
114
+
115
+ data = extract_json_from_stdout(stdout_text)
116
+
117
+ if not isinstance(data, dict):
118
+ raise RuntimeError("Inference returned non-dict JSON.")
119
+
120
+ if "ai_detection" not in data:
121
+ data["ai_detection"] = None
122
+
123
+ return data
124
+
125
+
126
+ # ==========
127
+ # FastAPI
128
+ # ==========
129
+
130
+ app = FastAPI(
131
+ title="Poster Design Analyzer API",
132
+ version="1.0.0",
133
+ description="API для анализа постеров и возврата 5 оценок и диагностического анализа."
134
+ )
135
+
136
+ app.add_middleware(
137
+ CORSMiddleware,
138
+ allow_origins=["*"],
139
+ allow_credentials=True,
140
+ allow_methods=["*"],
141
+ allow_headers=["*"],
142
+ )
143
+
144
+
145
+ @app.get("/")
146
+ def root() -> Dict[str, Any]:
147
+ return {
148
+ "service": "Poster Design Analyzer API",
149
+ "status": "ok",
150
+ "docs": "/docs",
151
+ "health": "/health",
152
+ "analyze": "/analyze",
153
+ }
154
+
155
+
156
+ @app.get("/health")
157
+ def health() -> Dict[str, str]:
158
+ return {"status": "ok"}
159
+
160
+
161
+ @app.post("/analyze")
162
+ async def analyze(file: UploadFile = File(...)) -> JSONResponse:
163
+ if not file.filename:
164
+ raise HTTPException(status_code=400, detail="Файл не передан.")
165
+
166
+ suffix = Path(file.filename).suffix.lower()
167
+ if suffix not in {".jpg", ".jpeg", ".png", ".webp", ".bmp"}:
168
+ raise HTTPException(status_code=400, detail="Неподдерживаемый формат файла.")
169
+
170
+ temp_dir = Path(tempfile.mkdtemp(prefix="poster_analyze_"))
171
+ temp_path = temp_dir / f"input{suffix}"
172
+
173
+ try:
174
+ contents = await file.read()
175
+ if not contents:
176
+ raise HTTPException(status_code=400, detail="Пустой файл.")
177
+
178
+ temp_path.write_bytes(contents)
179
+
180
+ # Валидация изображения
181
+ try:
182
+ with Image.open(temp_path) as img:
183
+ img.verify()
184
+ except Exception as e:
185
+ raise HTTPException(
186
+ status_code=400,
187
+ detail=f"Файл не является корректным изображением: {e}"
188
+ )
189
+
190
+ inference_result = run_model_inference(temp_path)
191
+
192
+ response = {
193
+ "filename": file.filename,
194
+ **inference_result,
195
+ }
196
+ return JSONResponse(content=response)
197
+
198
+ except HTTPException:
199
+ raise
200
+ except Exception as e:
201
+ raise HTTPException(status_code=500, detail=f"Ошибка инференса: {e}")
202
+ finally:
203
  shutil.rmtree(temp_dir, ignore_errors=True)