ftx7go commited on
Commit
8ca26d0
·
verified ·
1 Parent(s): 811280c

Create app.py

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