ftx7go commited on
Commit
f497038
·
verified ·
1 Parent(s): 0721cf7

Create app.py

Browse files
Files changed (1) hide show
  1. app.py +410 -0
app.py ADDED
@@ -0,0 +1,410 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from fastapi import FastAPI, File, UploadFile, Form
2
+ from fastapi.responses import HTMLResponse, Response
3
+ from transformers import pipeline
4
+ from PIL import Image, ImageDraw
5
+ import numpy as np
6
+ import io
7
+ import uvicorn
8
+ import base64
9
+ from reportlab.lib.pagesizes import letter
10
+ from reportlab.platypus import SimpleDocTemplate, Image as ReportLabImage, Paragraph, Spacer
11
+ from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle
12
+ from reportlab.lib.colors import red, blue, black
13
+ from reportlab.lib.units import inch
14
+
15
+ app = FastAPI()
16
+
17
+ # Chargement des modèles
18
+ def load_models():
19
+ return {
20
+ "KnochenAuge": pipeline("object-detection", model="D3STRON/bone-fracture-detr"),
21
+ "KnochenWächter": pipeline("image-classification", model="Heem2/bone-fracture-detection-using-xray"),
22
+ "RöntgenMeister": pipeline("image-classification",
23
+ model="nandodeomkar/autotrain-fracture-detection-using-google-vit-base-patch-16-54382127388")
24
+ }
25
+
26
+ models = load_models()
27
+
28
+ def translate_label(label):
29
+ translations = {
30
+ "fracture": "Knochenbruch",
31
+ "no fracture": "Kein Knochenbruch",
32
+ "normal": "Normal",
33
+ "abnormal": "Auffällig",
34
+ "F1": "Knochenbruch",
35
+ "NF": "Kein Knochenbruch"
36
+ }
37
+ return translations.get(label.lower(), label)
38
+
39
+ def create_heatmap_overlay(image, box, score):
40
+ overlay = Image.new('RGBA', image.size, (0, 0, 0, 0))
41
+ draw = ImageDraw.Draw(overlay)
42
+
43
+ x1, y1 = box['xmin'], box['ymin']
44
+ x2, y2 = box['xmax'], box['ymax']
45
+
46
+ if score > 0.8:
47
+ fill_color = (255, 0, 0, 100)
48
+ border_color = (255, 0, 0, 255)
49
+ elif score > 0.6:
50
+ fill_color = (255, 165, 0, 100)
51
+ border_color = (255, 165, 0, 255)
52
+ else:
53
+ fill_color = (255, 255, 0, 100)
54
+ border_color = (255, 255, 0, 255)
55
+
56
+ draw.rectangle([x1, y1, x2, y2], fill=fill_color)
57
+ draw.rectangle([x1, y1, x2, y2], outline=border_color, width=2)
58
+
59
+ return overlay
60
+
61
+ def draw_boxes(image, predictions):
62
+ result_image = image.copy().convert('RGBA')
63
+
64
+ for pred in predictions:
65
+ box = pred['box']
66
+ score = pred['score']
67
+
68
+ overlay = create_heatmap_overlay(image, box, score)
69
+ result_image = Image.alpha_composite(result_image, overlay)
70
+
71
+ draw = ImageDraw.Draw(result_image)
72
+ temp = 36.5 + (score * 2.5)
73
+ label = f"{translate_label(pred['label'])} ({score:.1%} • {temp:.1f}°C)"
74
+
75
+ text_bbox = draw.textbbox((box['xmin'], box['ymin']-20), label)
76
+ draw.rectangle(text_bbox, fill=(0, 0, 0, 180))
77
+
78
+ draw.text(
79
+ (box['xmin'], box['ymin']-20),
80
+ label,
81
+ fill=(255, 255, 255, 255)
82
+ )
83
+
84
+ return result_image
85
+
86
+ def image_to_base64(image):
87
+ buffered = io.BytesIO()
88
+ image.save(buffered, format="PNG")
89
+ img_str = base64.b64encode(buffered.getvalue()).decode()
90
+ return f"data:image/png;base64,{img_str}"
91
+
92
+ def generate_report(patient_name, analyzed_image_bytes, prediction, confidence):
93
+ buffer = io.BytesIO()
94
+ doc = SimpleDocTemplate(buffer, pagesize=letter)
95
+ styles = getSampleStyleSheet()
96
+ title_style = ParagraphStyle(
97
+ name='TitleStyle',
98
+ parent=styles['Normal'],
99
+ fontSize=16,
100
+ textColor=blue,
101
+ alignment=1 # Center alignment
102
+ )
103
+ heading_style = ParagraphStyle(
104
+ name='HeadingStyle',
105
+ parent=styles['Normal'],
106
+ fontSize=12,
107
+ textColor=red
108
+ )
109
+ prediction_style = ParagraphStyle(
110
+ name='PredictionStyle',
111
+ parent=styles['Normal'],
112
+ fontSize=14,
113
+ alignment=1
114
+ )
115
+
116
+ story = []
117
+
118
+ # Hospital Name
119
+ hospital_name = Paragraph("youesh hospital , mumbai ( west )", title_style)
120
+ story.append(hospital_name)
121
+ story.append(Spacer(1, 0.2*inch))
122
+
123
+ # Patient Greeting
124
+ greeting = Paragraph(f"hello , {patient_name} thank you for using our services this is your radiology report", heading_style)
125
+ story.append(greeting)
126
+ story.append(Spacer(1, 0.2*inch))
127
+
128
+ # Horizontal Line
129
+ story.append(Paragraph("<hr/>", styles['Normal']))
130
+ story.append(Spacer(1, 0.2*inch))
131
+
132
+ # Analyzed Image
133
+ img = ReportLabImage(io.BytesIO(analyzed_image_bytes), width=400, height=400, kind='direct')
134
+ story.append(img)
135
+ story.append(Spacer(1, 0.2*inch))
136
+
137
+ # Prediction
138
+ prediction_text = f"<b>Prediction:</b> {prediction.capitalize()}"
139
+ confidence_text = f"<b>Confidence:</b> {'Yes' if confidence > 0.6 else 'No'}"
140
+ story.append(Paragraph(prediction_text, prediction_style))
141
+ story.append(Paragraph(confidence_text, prediction_style))
142
+
143
+ doc.build(story)
144
+ buffer.seek(0)
145
+ return buffer.getvalue()
146
+
147
+ COMMON_STYLES = """
148
+ body {
149
+ font-family: system-ui, -apple-system, sans-serif;
150
+ background: #f0f2f5;
151
+ margin: 0;
152
+ padding: 20px;
153
+ color: #1a1a1a;
154
+ }
155
+ ::-webkit-scrollbar {
156
+ width: 8px;
157
+ height: 8px;
158
+ }
159
+
160
+ ::-webkit-scrollbar-track {
161
+ background: transparent;
162
+ }
163
+
164
+ ::-webkit-scrollbar-thumb {
165
+ background-color: rgba(156, 163, 175, 0.5);
166
+ border-radius: 4px;
167
+ }
168
+
169
+ .container {
170
+ max-width: 1200px;
171
+ margin: 0 auto;
172
+ background: white;
173
+ padding: 20px;
174
+ border-radius: 10px;
175
+ box-shadow: 0 2px 4px rgba(0,0,0,0.1);
176
+ }
177
+ .button {
178
+ background: #2d2d2d;
179
+ color: white;
180
+ border: none;
181
+ padding: 12px 30px;
182
+ border-radius: 8px;
183
+ cursor: pointer;
184
+ font-size: 1.1em;
185
+ transition: all 0.3s ease;
186
+ position: relative;
187
+ }
188
+ .button:hover {
189
+ background: #404040;
190
+ }
191
+ @keyframes progress {
192
+ 0% { width: 0; }
193
+ 100% { width: 100%; }
194
+ }
195
+ .button-progress {
196
+ position: absolute;
197
+ bottom: 0;
198
+ left: 0;
199
+ height: 4px;
200
+ background: rgba(255, 255, 255, 0.5);
201
+ width: 0;
202
+ }
203
+ .button:active .button-progress {
204
+ animation: progress 2s linear forwards;
205
+ }
206
+ img {
207
+ max-width: 100%;
208
+ height: auto;
209
+ border-radius: 8px;
210
+ }
211
+ @keyframes blink {
212
+ 0% { opacity: 1; }
213
+ 50% { opacity: 0; }
214
+ 100% { opacity: 1; }
215
+ }
216
+ #loading {
217
+ display: none;
218
+ color: white;
219
+ margin-top: 10px;
220
+ animation: blink 1s infinite;
221
+ text-align: center;
222
+ }
223
+ """
224
+
225
+ @app.get("/", response_class=HTMLResponse)
226
+ async def main():
227
+ content = f"""
228
+ <!DOCTYPE html>
229
+ <html>
230
+ <head>
231
+ <title>Fraktur Detektion</title>
232
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
233
+ <style>
234
+ {COMMON_STYLES}
235
+
236
+ .input-group {{
237
+ margin-bottom: 20px;
238
+ }}
239
+ .input-group label {{
240
+ display: block;
241
+ margin-bottom: 5px;
242
+ color: #404040;
243
+ font-weight: bold;
244
+ }}
245
+ .input-group input[type="text"],
246
+ .input-group input[type="email"] {{
247
+ width: calc(100% - 22px);
248
+ padding: 10px;
249
+ border: 1px solid #ccc;
250
+ border-radius: 4px;
251
+ font-size: 1em;
252
+ }}
253
+
254
+ .upload-section {{
255
+ background: #2d2d2d;
256
+ padding: 40px;
257
+ border-radius: 12px;
258
+ margin: 20px 0;
259
+ text-align: center;
260
+ border: 2px dashed #404040;
261
+ transition: all 0.3s ease;
262
+ color: white;
263
+ }}
264
+ .upload-section:hover {{
265
+ border-color: #555;
266
+ }}
267
+ input[type="file"] {{
268
+ font-size: 1.1em;
269
+ margin: 20px 0;
270
+ color: white;
271
+ }}
272
+ input[type="file"]::file-selector-button {{
273
+ font-size: 1em;
274
+ padding: 10px 20px;
275
+ border-radius: 8px;
276
+ border: 1px solid #404040;
277
+ background: #2d2d2d;
278
+ color: white;
279
+ transition: all 0.3s ease;
280
+ cursor: pointer;
281
+ }}
282
+ input[type="file"]::file-selector-button:hover {{
283
+ background: #404040;
284
+ }}
285
+ .confidence-slider {{
286
+ width: 100%;
287
+ max-width: 300px;
288
+ margin: 20px auto;
289
+ }}
290
+ input[type="range"] {{
291
+ width: 100%;
292
+ height: 8px;
293
+ border-radius: 4px;
294
+ background: #404040;
295
+ outline: none;
296
+ transition: all 0.3s ease;
297
+ -webkit-appearance: none;
298
+ }}
299
+ input[type="range"]::-webkit-slider-thumb {{
300
+ -webkit-appearance: none;
301
+ width: 20px;
302
+ height: 20px;
303
+ border-radius: 50%;
304
+ background: white;
305
+ cursor: pointer;
306
+ border: none;
307
+ }}
308
+ </style>
309
+ </head>
310
+ <body>
311
+ <div class="container">
312
+ <form action="/analyze" method="post" enctype="multipart/form-data" onsubmit="document.getElementById('loading').style.display = 'block';">
313
+ <div class="input-group">
314
+ <label for="name">Name:</label>
315
+ <input type="text" id="name" name="name" required>
316
+ </div>
317
+ <div class="input-group">
318
+ <label for="email">Email Address:</label>
319
+ <input type="email" id="email" name="email" required>
320
+ </div>
321
+ <div class="upload-section">
322
+ <div>
323
+ <input type="file" name="file" accept="image/*" required>
324
+ </div>
325
+ <div class="confidence-slider">
326
+ <label for="threshold">Konfidenzschwelle: <span id="thresholdValue">0.60</span></label>
327
+ <input type="range" id="threshold" name="threshold"
328
+ min="0" max="1" step="0.05" value="0.60"
329
+ oninput="document.getElementById('thresholdValue').textContent = parseFloat(this.value).toFixed(2)">
330
+ </div>
331
+ <button type="submit" class="button">
332
+ Generate Report
333
+ <div class="button-progress"></div>
334
+ </button>
335
+ <div id="loading">Loading...</div>
336
+ </div>
337
+ </form>
338
+ </div>
339
+ </body>
340
+ </html>
341
+ """
342
+ return content
343
+
344
+ @app.post("/analyze", response_class=Response)
345
+ async def analyze_file(name: str = Form(...), email: str = Form(...), file: UploadFile = File(...), threshold: float = Form(0.6)):
346
+ try:
347
+ contents = await file.read()
348
+ image = Image.open(io.BytesIO(contents))
349
+
350
+ predictions_watcher = models["KnochenWächter"](image)
351
+ predictions_master = models["RöntgenMeister"](image)
352
+ predictions_locator = models["KnochenAuge"](image)
353
+
354
+ filtered_preds = [p for p in predictions_locator if p['score'] >= threshold]
355
+ analyzed_image = image
356
+ overall_prediction = "No Fracture"
357
+ max_confidence = 0.0
358
+
359
+ if filtered_preds:
360
+ analyzed_image = draw_boxes(image, filtered_preds)
361
+ overall_prediction = "Fracture Detected"
362
+ max_confidence = max([p['score'] for p in filtered_preds])
363
+
364
+ image_stream = io.BytesIO()
365
+ analyzed_image.save(image_stream, format="PNG")
366
+ image_bytes = image_stream.getvalue()
367
+
368
+ pdf_report = generate_report(name, image_bytes, overall_prediction, max_confidence)
369
+
370
+ headers = {
371
+ 'Content-Disposition': 'attachment; filename="report.pdf"'
372
+ }
373
+ return Response(content=pdf_report, headers=headers, media_type="application/pdf")
374
+
375
+ except Exception as e:
376
+ error_html = f"""
377
+ <!DOCTYPE html>
378
+ <html>
379
+ <head>
380
+ <title>Fehler</title>
381
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
382
+ <style>
383
+ {COMMON_STYLES}
384
+ .error-box {{
385
+ background: #fee2e2;
386
+ border: 1px solid #ef4444;
387
+ padding: 20px;
388
+ border-radius: 8px;
389
+ margin: 20px 0;
390
+ }}
391
+ </style>
392
+ </head>
393
+ <body>
394
+ <div class="container">
395
+ <div class="error-box">
396
+ <h3>Fehler</h3>
397
+ <p>{str(e)}</p>
398
+ </div>
399
+ <a href="/" class="button back-button">
400
+ ← Zurück
401
+ <div class="button-progress"></div>
402
+ </a>
403
+ </div>
404
+ </body>
405
+ </html>
406
+ """
407
+ return HTMLResponse(content=error_html)
408
+
409
+ if __name__ == "__main__":
410
+ uvicorn.run(app, host="0.0.0.0", port=7860)