File size: 7,730 Bytes
0be9545
 
 
 
 
 
 
 
 
 
 
 
 
361af0a
0be9545
 
 
 
995459b
0be9545
fe9b8a5
0be9545
 
fe9b8a5
 
995459b
0be9545
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
995459b
0be9545
 
 
 
 
995459b
0be9545
 
995459b
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
0be9545
995459b
0be9545
995459b
0be9545
995459b
0be9545
995459b
0be9545
995459b
0be9545
995459b
0be9545
995459b
 
0be9545
995459b
0be9545
995459b
 
 
 
 
 
 
 
 
 
 
 
0be9545
 
995459b
0be9545
 
 
 
995459b
0be9545
 
 
 
 
 
 
 
995459b
0be9545
 
 
 
 
 
 
fe9b8a5
0be9545
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
995459b
0be9545
 
 
 
995459b
0be9545
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
c888602
0be9545
fe9b8a5
 
c888602
fe9b8a5
 
0be9545
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
c888602
 
995459b
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
import os
import base64
import requests
import json
from flask import Flask, render_template, request, jsonify
from PIL import Image
import io

app = Flask(__name__)
app.config['MAX_CONTENT_LENGTH'] = 10 * 1024 * 1024  # 10MB max file size

# Gemini API configuration
GEMINI_API_KEY = os.environ.get('GEMINI_API_KEY')
GEMINI_API_URL = "https://generativelanguage.googleapis.com/v1beta/models/gemini-2.5-flash-image-preview:generateContent"

ALLOWED_EXTENSIONS = {'png', 'jpg', 'jpeg', 'gif'}

def allowed_file(filename):
    return '.' in filename and filename.rsplit('.', 1)[1].lower() in ALLOWED_EXTENSIONS

def optimize_image(image_bytes, max_size=(1024, 1024), quality=85):
    """优化图像大小和质量"""
    try:
        # 使用 io.BytesIO 将字节数据转换为文件对象,供 Pillow 使用
        img = Image.open(io.BytesIO(image_bytes))

        if img.mode in ('RGBA', 'LA'):
            background = Image.new('RGB', img.size, (255, 255, 255))
            if img.mode == 'RGBA':
                background.paste(img, mask=img.split()[-1])
            else:
                background.paste(img, mask=img.split()[-1])
            img = background
        
        img.thumbnail(max_size, Image.Resampling.LANCZOS)
        
        img_byte_arr = io.BytesIO()
        img.save(img_byte_arr, format='JPEG', quality=quality, optimize=True)
        img_byte_arr.seek(0)
        
        return img_byte_arr.getvalue()
    except Exception as e:
        print(f"Image optimization error: {e}")
        return image_bytes  # 优化失败,返回原始字节数据

def call_gemini_api(text_content=None, image_data=None):
    """调用Gemini API进行分析"""
    if not GEMINI_API_KEY:
        return {"error": "未配置Gemini API密钥,请设置环境变量GEMINI_API_KEY"}

    system_prompt = """你是一位经验丰富的皮肤科专家医生,具有多年的临床诊疗经验。请基于提供的信息进行专业的皮肤病分析。

    分析要求:

    仔细观察皮肤病变的形态、颜色、分布、边界等特征

    结合患者描述的症状和病史

    提供可能的诊断方向(按可能性大小排序)

    给出详细的诊疗建议

    必要时建议进一步检查

    提醒患者注意事项
    请按以下格式回复:

    🔍 图像观察分析

    [详细描述观察到的皮肤病变特征]

    📋 症状综合评估

    [结合文字描述的症状分析]

    🎯 可能诊断

    最可能诊断: [诊断名称] - 可能性: X%

    依据: [具体理由]

    次要考虑: [其他可能诊断]

    💊 治疗建议
    药物治疗

    [具体用药建议]

    生活护理

    [日常护理要点]

    🏥 进一步检查建议

    [如需要其他检查,请列出]

    ⚠️ 注意事项
    [重要提醒和注意事项]

    免责声明: 此分析仅供临床参考,最终诊断需结合完整病史、体格检查等综合判断。建议患者及时就医,接受专业医师诊疗。"""

    parts = []

    if text_content:
        parts.append({"text": f"{system_prompt}\n\n患者症状描述:{text_content}"})
    else:
        parts.append({"text": system_prompt + "\n\n请基于图像进行分析。"})

    if image_data:
        image_base64 = base64.b64encode(image_data).decode('utf-8')
        parts.append({
            "inline_data": {
                "mime_type": "image/jpeg",
                "data": image_base64
            }
        })

    request_body = {
        "contents": [
            {
                "parts": parts
            }
        ],
        "generationConfig": {
            "temperature": 0.1,
            "topK": 40,
            "topP": 0.95,
            "maxOutputTokens": 2048,
        },
        "safetySettings": [
            {
                "category": "HARM_CATEGORY_HARASSMENT",
                "threshold": "BLOCK_MEDIUM_AND_ABOVE"
            },
            {
                "category": "HARM_CATEGORY_HATE_SPEECH",
                "threshold": "BLOCK_MEDIUM_AND_ABOVE"
            },
            {
                "category": "HARM_CATEGORY_SEXUALLY_EXPLICIT",
                "threshold": "BLOCK_MEDIUM_AND_ABOVE"
            },
            {
                "category": "HARM_CATEGORY_DANGEROUS_CONTENT",
                "threshold": "BLOCK_MEDIUM_AND_ABOVE"
            }
        ]
    }

    headers = {
        'Content-Type': 'application/json',
        'x-goog-api-key': GEMINI_API_KEY
    }

    try:
        response = requests.post(
            GEMINI_API_URL,
            headers=headers,
            json=request_body,
            timeout=30
        )
        
        response.raise_for_status()
        result = response.json()
        
        if 'candidates' in result and len(result['candidates']) > 0:
            content = result['candidates'][0]['content']['parts'][0]['text']
            return {"result": content}
        else:
            return {"error": "API返回了空的响应"}
            
    except requests.exceptions.Timeout:
        return {"error": "请求超时,请稍后重试"}
    except requests.exceptions.RequestException as e:
        return {"error": f"网络请求失败: {str(e)}"}
    except json.JSONDecodeError:
        return {"error": "API返回了无效的JSON格式"}
    except Exception as e:
        return {"error": f"处理响应时发生错误: {str(e)}"}

@app.route('/')
def index():
    """主页路由"""
    return render_template('index.html')

@app.route('/analyze', methods=['POST'])
def analyze():
    """分析皮肤图像和症状的API端点"""
    try:
        text_content = request.form.get('text', '').strip()
        image_data = None
        
        if 'image' in request.files:
            file = request.files['image']
            if file.filename != '':
                if allowed_file(file.filename):
                    try:
                        # 核心修复:一次性将文件流读入内存
                        original_image_bytes = file.read()
                        
                        # 调用优化函数,它现在接受字节数据
                        image_data = optimize_image(original_image_bytes)
                    except Exception as e:
                        return jsonify({"error": f"图像处理失败: {str(e)}"}), 400
                else:
                    return jsonify({"error": "不支持的文件格式,请上传PNG、JPG、JPEG或GIF格式的图像"}), 400
        
        if not text_content and not image_data:
            return jsonify({"error": "请至少提供症状描述或上传皮肤图像"}), 400
        
        result = call_gemini_api(text_content, image_data)
        
        return jsonify(result)
        
    except Exception as e:
        return jsonify({"error": f"服务器内部错误: {str(e)}"}), 500

@app.route('/health')
def health_check():
    """健康检查端点"""
    return jsonify({
        "status": "healthy",
        "service": "dermatology-assistant",
        "version": "1.0.0"
    })

@app.errorhandler(413)
def too_large(e):
    return jsonify({"error": "上传的文件过大,请确保文件小于10MB"}), 413

@app.errorhandler(404)
def not_found(e):
    return jsonify({"error": "请求的资源不存在"}), 404

@app.errorhandler(500)
def internal_error(e):
    return jsonify({"error": "服务器内部错误"}), 500

if __name__ == '__main__':
    os.makedirs('templates', exist_ok=True)
    if not GEMINI_API_KEY:
        print("⚠️  警告: 未设置GEMINI_API_KEY环境变量")
        print("    请设置环境变量: export GEMINI_API_KEY=your_api_key")
    port = int(os.environ.get('PORT', 7860))
    app.run(host='0.0.0.0', port=port, debug=False)