leonsimon23 commited on
Commit
0be9545
·
verified ·
1 Parent(s): def98cb

Create app.py

Browse files
Files changed (1) hide show
  1. app.py +255 -0
app.py ADDED
@@ -0,0 +1,255 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import base64
3
+ import requests
4
+ import json
5
+ from flask import Flask, render_template, request, jsonify
6
+ from werkzeug.utils import secure_filename
7
+ from PIL import Image
8
+ import io
9
+
10
+ app = Flask(__name__)
11
+ app.config['MAX_CONTENT_LENGTH'] = 10 * 1024 * 1024 # 10MB max file size
12
+
13
+ # Gemini API configuration
14
+ GEMINI_API_KEY = os.environ.get('GEMINI_API_KEY')
15
+ GEMINI_API_URL = "https://generativelanguage.googleapis.com/v1beta/models/gemini-2.0-flash-exp:generateContent"
16
+
17
+ ALLOWED_EXTENSIONS = {'png', 'jpg', 'jpeg', 'gif'}
18
+
19
+ def allowed_file(filename):
20
+ return '.' in filename and \
21
+ filename.rsplit('.', 1)[1].lower() in ALLOWED_EXTENSIONS
22
+
23
+ def optimize_image(image_file, max_size=(1024, 1024), quality=85):
24
+ """优化图像大小和质量"""
25
+ try:
26
+ img = Image.open(image_file)
27
+
28
+ # Convert RGBA to RGB if necessary
29
+ if img.mode in ('RGBA', 'LA'):
30
+ background = Image.new('RGB', img.size, (255, 255, 255))
31
+ if img.mode == 'RGBA':
32
+ background.paste(img, mask=img.split()[-1])
33
+ else:
34
+ background.paste(img, mask=img.split()[-1])
35
+ img = background
36
+
37
+ # Resize if too large
38
+ img.thumbnail(max_size, Image.Resampling.LANCZOS)
39
+
40
+ # Save to bytes
41
+ img_byte_arr = io.BytesIO()
42
+ img.save(img_byte_arr, format='JPEG', quality=quality, optimize=True)
43
+ img_byte_arr.seek(0)
44
+
45
+ return img_byte_arr.getvalue()
46
+ except Exception as e:
47
+ print(f"Image optimization error: {e}")
48
+ # Fallback: return original file data
49
+ image_file.seek(0)
50
+ return image_file.read()
51
+
52
+ def call_gemini_api(text_content=None, image_data=None):
53
+ """调用Gemini API进行分析"""
54
+
55
+ if not GEMINI_API_KEY:
56
+ return {"error": "未配置Gemini API密钥,请设置环境变量GEMINI_API_KEY"}
57
+
58
+ # 构建专业的皮肤科医生prompt
59
+ system_prompt = """你是一位经验丰富的皮肤科专家医生,具有多年的临床诊疗经验。请基于提供的信息进行专业的皮肤病分析。
60
+
61
+ 分析要求:
62
+ 1. 仔细观察皮肤病变的形态、颜色、分布、边界等特征
63
+ 2. 结合患者描述的症状和病史
64
+ 3. 提供可能的诊断方向(按可能性大小排序)
65
+ 4. 给出详细的诊疗建议
66
+ 5. 必要时建议进一步检查
67
+ 6. 提醒患者注意事项
68
+
69
+ 请按以下格式回复:
70
+
71
+ ## 🔍 图像观察分析
72
+ [详细描述观察到的皮肤病变特征]
73
+
74
+ ## 📋 症状综合评估
75
+ [结合文字描述的症状分析]
76
+
77
+ ## 🎯 可能诊断
78
+ 1. **最可能诊断**: [诊断名称] - 可能性: X%
79
+ - 依据: [具体理由]
80
+
81
+ 2. **次要考虑**: [其他可能诊断]
82
+
83
+ ## 💊 治疗建议
84
+ ### 药物治疗
85
+ - [具体用药建议]
86
+
87
+ ### 生活护理
88
+ - [日常护理要点]
89
+
90
+ ## 🏥 进一步检查建议
91
+ [如需要其他检查,请列出]
92
+
93
+ ## ⚠️ 注意事项
94
+ [重要提醒和注意事项]
95
+
96
+ ---
97
+ **免责声明**: 此分析仅供临床参考,最终诊断需结合完整病史、体格检查等综合判断。建议患者及时就医,接受专业医师诊疗。"""
98
+
99
+ # 构建请求内容
100
+ parts = []
101
+
102
+ # 添加系统提示和文本内容
103
+ if text_content:
104
+ parts.append({"text": f"{system_prompt}\n\n患者症状描述:{text_content}"})
105
+ else:
106
+ parts.append({"text": system_prompt + "\n\n请基于图像进行分析。"})
107
+
108
+ # 添加图像数据
109
+ if image_data:
110
+ # 转换为base64
111
+ image_base64 = base64.b64encode(image_data).decode('utf-8')
112
+ parts.append({
113
+ "inline_data": {
114
+ "mime_type": "image/jpeg",
115
+ "data": image_base64
116
+ }
117
+ })
118
+
119
+ # 构建请求体
120
+ request_body = {
121
+ "contents": [
122
+ {
123
+ "parts": parts
124
+ }
125
+ ],
126
+ "generationConfig": {
127
+ "temperature": 0.1, # 降低随机性,提高专业性
128
+ "topK": 40,
129
+ "topP": 0.95,
130
+ "maxOutputTokens": 2048,
131
+ },
132
+ "safetySettings": [
133
+ {
134
+ "category": "HARM_CATEGORY_HARASSMENT",
135
+ "threshold": "BLOCK_MEDIUM_AND_ABOVE"
136
+ },
137
+ {
138
+ "category": "HARM_CATEGORY_HATE_SPEECH",
139
+ "threshold": "BLOCK_MEDIUM_AND_ABOVE"
140
+ },
141
+ {
142
+ "category": "HARM_CATEGORY_SEXUALLY_EXPLICIT",
143
+ "threshold": "BLOCK_MEDIUM_AND_ABOVE"
144
+ },
145
+ {
146
+ "category": "HARM_CATEGORY_DANGEROUS_CONTENT",
147
+ "threshold": "BLOCK_MEDIUM_AND_ABOVE"
148
+ }
149
+ ]
150
+ }
151
+
152
+ # 发送请求
153
+ headers = {
154
+ 'Content-Type': 'application/json',
155
+ 'x-goog-api-key': GEMINI_API_KEY
156
+ }
157
+
158
+ try:
159
+ response = requests.post(
160
+ GEMINI_API_URL,
161
+ headers=headers,
162
+ json=request_body,
163
+ timeout=30
164
+ )
165
+
166
+ response.raise_for_status()
167
+ result = response.json()
168
+
169
+ # 解析响应
170
+ if 'candidates' in result and len(result['candidates']) > 0:
171
+ content = result['candidates'][0]['content']['parts'][0]['text']
172
+ return {"result": content}
173
+ else:
174
+ return {"error": "API返回了空的响应"}
175
+
176
+ except requests.exceptions.Timeout:
177
+ return {"error": "请求超时,请稍后重试"}
178
+ except requests.exceptions.RequestException as e:
179
+ return {"error": f"网络请求失败: {str(e)}"}
180
+ except json.JSONDecodeError:
181
+ return {"error": "API返回了无效的JSON格式"}
182
+ except Exception as e:
183
+ return {"error": f"处理响应时发生错误: {str(e)}"}
184
+
185
+ @app.route('/')
186
+ def index():
187
+ """主页路由"""
188
+ return render_template('index.html')
189
+
190
+ @app.route('/analyze', methods=['POST'])
191
+ def analyze():
192
+ """分析皮肤图像和症状的API端点"""
193
+
194
+ try:
195
+ text_content = request.form.get('text', '').strip()
196
+ image_data = None
197
+
198
+ # 处理上传的图像
199
+ if 'image' in request.files:
200
+ file = request.files['image']
201
+ if file.filename != '':
202
+ if file and allowed_file(file.filename):
203
+ try:
204
+ # 优化图像
205
+ image_data = optimize_image(file)
206
+ except Exception as e:
207
+ return jsonify({"error": f"图像处理失败: {str(e)}"}), 400
208
+ else:
209
+ return jsonify({"error": "不支持的文件格式,请上传PNG、JPG、JPEG或GIF格式的图像"}), 400
210
+
211
+ # 验证输入
212
+ if not text_content and not image_data:
213
+ return jsonify({"error": "请至少提供症状描述或上传皮肤图像"}), 400
214
+
215
+ # 调用Gemini API
216
+ result = call_gemini_api(text_content, image_data)
217
+
218
+ return jsonify(result)
219
+
220
+ except Exception as e:
221
+ return jsonify({"error": f"服务器内部错误: {str(e)}"}), 500
222
+
223
+ @app.route('/health')
224
+ def health_check():
225
+ """健康检查端点"""
226
+ return jsonify({
227
+ "status": "healthy",
228
+ "service": "dermatology-assistant",
229
+ "version": "1.0.0"
230
+ })
231
+
232
+ @app.errorhandler(413)
233
+ def too_large(e):
234
+ return jsonify({"error": "上传的文件过大,请确保文件小于10MB"}), 413
235
+
236
+ @app.errorhandler(404)
237
+ def not_found(e):
238
+ return jsonify({"error": "请求的资源不存在"}), 404
239
+
240
+ @app.errorhandler(500)
241
+ def internal_error(e):
242
+ return jsonify({"error": "服务器内部错误"}), 500
243
+
244
+ if __name__ == '__main__':
245
+ # 确保模板目录存在
246
+ os.makedirs('templates', exist_ok=True)
247
+
248
+ # 检查API密钥
249
+ if not GEMINI_API_KEY:
250
+ print("⚠️ 警告: 未设置GEMINI_API_KEY环境变量")
251
+ print(" 请设置环境变量: export GEMINI_API_KEY=your_api_key")
252
+
253
+ # 启动应用
254
+ port = int(os.environ.get('PORT', 7860)) # Hugging Face默认端口
255
+ app.run(host='0.0.0.0', port=port, debug=False)