yassonee commited on
Commit
10cfdb2
·
verified ·
1 Parent(s): 65e9027

Create app.py

Browse files
Files changed (1) hide show
  1. app.py +301 -0
app.py ADDED
@@ -0,0 +1,301 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from fastapi import FastAPI, File, UploadFile
2
+ from fastapi.responses import HTMLResponse
3
+ from transformers import AutoImageProcessor, AutoModelForImageClassification, pipeline
4
+ from PIL import Image
5
+ import io
6
+ import uvicorn
7
+ import base64
8
+
9
+ app = FastAPI()
10
+
11
+ # Chargement des modèles
12
+ def load_models():
13
+ return {
14
+ "chest_classifier": pipeline("image-classification", model="codewithdark/vit-chest-xray")
15
+ }
16
+
17
+ models = load_models()
18
+
19
+ def image_to_base64(image):
20
+ buffered = io.BytesIO()
21
+ image.save(buffered, format="PNG")
22
+ img_str = base64.b64encode(buffered.getvalue()).decode()
23
+ return f"data:image/png;base64,{img_str}"
24
+
25
+ COMMON_STYLES = """
26
+ body {
27
+ font-family: system-ui, -apple-system, sans-serif;
28
+ background: #f0f2f5;
29
+ margin: 0;
30
+ padding: 20px;
31
+ color: #1a1a1a;
32
+ }
33
+ ::-webkit-scrollbar {
34
+ width: 8px;
35
+ height: 8px;
36
+ }
37
+
38
+ ::-webkit-scrollbar-track {
39
+ background: transparent;
40
+ }
41
+
42
+ ::-webkit-scrollbar-thumb {
43
+ background-color: rgba(156, 163, 175, 0.5);
44
+ border-radius: 4px;
45
+ }
46
+
47
+ .container {
48
+ max-width: 1200px;
49
+ margin: 0 auto;
50
+ background: white;
51
+ padding: 20px;
52
+ border-radius: 10px;
53
+ box-shadow: 0 2px 4px rgba(0,0,0,0.1);
54
+ }
55
+ .button {
56
+ background: #2d2d2d;
57
+ color: white;
58
+ border: none;
59
+ padding: 12px 30px;
60
+ border-radius: 8px;
61
+ cursor: pointer;
62
+ font-size: 1.1em;
63
+ transition: all 0.3s ease;
64
+ position: relative;
65
+ }
66
+ .button:hover {
67
+ background: #404040;
68
+ }
69
+ @keyframes progress {
70
+ 0% { width: 0; }
71
+ 100% { width: 100%; }
72
+ }
73
+ @keyframes blink {
74
+ 0% { opacity: 1; }
75
+ 50% { opacity: 0; }
76
+ 100% { opacity: 1; }
77
+ }
78
+ #loading {
79
+ display: none;
80
+ color: white;
81
+ margin-top: 10px;
82
+ animation: blink 1s infinite;
83
+ text-align: center;
84
+ }
85
+ .button-progress {
86
+ position: absolute;
87
+ bottom: 0;
88
+ left: 0;
89
+ height: 4px;
90
+ background: rgba(255, 255, 255, 0.5);
91
+ width: 0;
92
+ }
93
+ .button:active .button-progress {
94
+ animation: progress 2s linear forwards;
95
+ }
96
+ img {
97
+ max-width: 100%;
98
+ height: auto;
99
+ border-radius: 8px;
100
+ }
101
+ """
102
+
103
+ @app.get("/", response_class=HTMLResponse)
104
+ async def main():
105
+ content = f"""
106
+ <!DOCTYPE html>
107
+ <html>
108
+ <head>
109
+ <title>Chest X-Ray Analysis</title>
110
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
111
+ <style>
112
+ {COMMON_STYLES}
113
+
114
+ .upload-section {{
115
+ background: #2d2d2d;
116
+ padding: 40px;
117
+ border-radius: 12px;
118
+ margin: 20px 0;
119
+ text-align: center;
120
+ border: 2px dashed #404040;
121
+ transition: all 0.3s ease;
122
+ color: white;
123
+ }}
124
+ .upload-section:hover {{
125
+ border-color: #555;
126
+ }}
127
+ input[type="file"] {{
128
+ font-size: 1.1em;
129
+ margin: 20px 0;
130
+ color: white;
131
+ }}
132
+ input[type="file"]::file-selector-button {{
133
+ font-size: 1em;
134
+ padding: 10px 20px;
135
+ border-radius: 8px;
136
+ border: 1px solid #404040;
137
+ background: #2d2d2d;
138
+ color: white;
139
+ transition: all 0.3s ease;
140
+ cursor: pointer;
141
+ }}
142
+ input[type="file"]::file-selector-button:hover {{
143
+ background: #404040;
144
+ }}
145
+ .preview-image {{
146
+ max-width: 300px;
147
+ margin: 20px auto;
148
+ display: none;
149
+ }}
150
+ </style>
151
+ </head>
152
+ <body>
153
+ <div class="container">
154
+ <div class="upload-section">
155
+ <form action="/analyze" method="post" enctype="multipart/form-data" onsubmit="document.getElementById('loading').style.display = 'block';">
156
+ <div>
157
+ <input type="file" name="file" accept="image/*" required
158
+ onchange="document.getElementById('preview').src = window.URL.createObjectURL(this.files[0]);
159
+ document.getElementById('preview').style.display = 'block';">
160
+ </div>
161
+ <img id="preview" class="preview-image" src="" alt="Preview">
162
+ <button type="submit" class="button">
163
+ Analyze X-Ray
164
+ <div class="button-progress"></div>
165
+ </button>
166
+ <div id="loading">Loading...</div>
167
+ </form>
168
+ </div>
169
+ </div>
170
+ </body>
171
+ </html>
172
+ """
173
+ return content
174
+
175
+ @app.post("/analyze", response_class=HTMLResponse)
176
+ async def analyze_file(file: UploadFile = File(...)):
177
+ try:
178
+ contents = await file.read()
179
+ image = Image.open(io.BytesIO(contents))
180
+
181
+ # Get predictions from the model
182
+ predictions = models["chest_classifier"](image)
183
+
184
+ result_image_b64 = image_to_base64(image)
185
+
186
+ results_html = f"""
187
+ <!DOCTYPE html>
188
+ <html>
189
+ <head>
190
+ <title>Results</title>
191
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
192
+ <style>
193
+ {COMMON_STYLES}
194
+
195
+ .results-grid {{
196
+ display: grid;
197
+ grid-template-columns: 1fr 1fr;
198
+ gap: 20px;
199
+ margin-top: 20px;
200
+ }}
201
+ .result-box {{
202
+ background: white;
203
+ padding: 20px;
204
+ border-radius: 12px;
205
+ margin: 10px 0;
206
+ border: 1px solid #e9ecef;
207
+ }}
208
+ .score-high {{
209
+ color: #0066cc;
210
+ font-weight: bold;
211
+ }}
212
+ .score-medium {{
213
+ color: #ffa500;
214
+ font-weight: bold;
215
+ }}
216
+ .back-button {{
217
+ display: inline-block;
218
+ text-decoration: none;
219
+ margin-top: 20px;
220
+ }}
221
+ h3 {{
222
+ color: #0066cc;
223
+ margin-top: 0;
224
+ }}
225
+ @media (max-width: 768px) {{
226
+ .results-grid {{
227
+ grid-template-columns: 1fr;
228
+ }}
229
+ }}
230
+ </style>
231
+ </head>
232
+ <body>
233
+ <div class="container">
234
+ <div class="results-grid">
235
+ <div class="result-box">
236
+ <h3>Analysis Results</h3>
237
+ """
238
+
239
+ for pred in predictions:
240
+ confidence_class = "score-high" if pred['score'] > 0.7 else "score-medium"
241
+ results_html += f"""
242
+ <div>
243
+ <span class="{confidence_class}">{pred['score']:.1%}</span> -
244
+ {pred['label']}
245
+ </div>
246
+ """
247
+
248
+ results_html += f"""
249
+ </div>
250
+ <div class='result-box'>
251
+ <h3>X-Ray Image</h3>
252
+ <img src="{result_image_b64}" alt="Analyzed X-Ray">
253
+ </div>
254
+ </div>
255
+
256
+ <a href="/" class="button back-button">
257
+ ← Back
258
+ <div class="button-progress"></div>
259
+ </a>
260
+ </div>
261
+ </body>
262
+ </html>
263
+ """
264
+
265
+ return results_html
266
+
267
+ except Exception as e:
268
+ return f"""
269
+ <!DOCTYPE html>
270
+ <html>
271
+ <head>
272
+ <title>Error</title>
273
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
274
+ <style>
275
+ {COMMON_STYLES}
276
+ .error-box {{
277
+ background: #fee2e2;
278
+ border: 1px solid #ef4444;
279
+ padding: 20px;
280
+ border-radius: 8px;
281
+ margin: 20px 0;
282
+ }}
283
+ </style>
284
+ </head>
285
+ <body>
286
+ <div class="container">
287
+ <div class="error-box">
288
+ <h3>Error</h3>
289
+ <p>{str(e)}</p>
290
+ </div>
291
+ <a href="/" class="button back-button">
292
+ ← Back
293
+ <div class="button-progress"></div>
294
+ </a>
295
+ </div>
296
+ </body>
297
+ </html>
298
+ """
299
+
300
+ if __name__ == "__main__":
301
+ uvicorn.run(app, host="0.0.0.0", port=7860)