ftx7go commited on
Commit
c08364f
·
verified ·
1 Parent(s): f2b4347

Create app.py

Browse files
Files changed (1) hide show
  1. app.py +378 -0
app.py ADDED
@@ -0,0 +1,378 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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: #404040; /* Changed button background color */
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: #555;
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
+ width: calc(100% - 22px);
247
+ padding: 10px;
248
+ border: 1px solid #ccc;
249
+ border-radius: 4px;
250
+ font-size: 1em;
251
+ }
252
+
253
+ .upload-section {
254
+ background: #2d2d2d;
255
+ padding: 40px;
256
+ border-radius: 12px;
257
+ margin: 20px 0;
258
+ text-align: center;
259
+ border: 2px dashed #404040;
260
+ transition: all 0.3s ease;
261
+ color: white;
262
+ }
263
+ .upload-section:hover {
264
+ border-color: #555;
265
+ }
266
+ input[type="file"] {
267
+ font-size: 1.1em;
268
+ margin: 20px 0;
269
+ color: white;
270
+ }
271
+ input[type="file"]::file-selector-button {
272
+ font-size: 1em;
273
+ padding: 10px 20px;
274
+ border-radius: 8px;
275
+ border: 1px solid #404040;
276
+ background: #2d2d2d;
277
+ color: white;
278
+ transition: all 0.3s ease;
279
+ cursor: pointer;
280
+ }
281
+ input[type="file"]::file-selector-button:hover {
282
+ background: #404040;
283
+ }
284
+ </style>
285
+ </head>
286
+ <body>
287
+ <div class="container">
288
+ <form action="/analyze" method="post" enctype="multipart/form-data" onsubmit="document.getElementById('loading').style.display = 'block';">
289
+ <div class="input-group">
290
+ <label for="name">Name:</label>
291
+ <input type="text" id="name" name="name" required>
292
+ </div>
293
+ <div class="upload-section">
294
+ <div>
295
+ <input type="file" name="file" accept="image/*" required>
296
+ </div>
297
+ <button type="submit" class="button">
298
+ Generate Report
299
+ <div class="button-progress"></div>
300
+ </button>
301
+ <div id="loading">Loading...</div>
302
+ </div>
303
+ </form>
304
+ </div>
305
+ </body>
306
+ </html>
307
+ """
308
+ return content
309
+
310
+ @app.post("/analyze", response_class=Response)
311
+ async def analyze_file(name: str = Form(...), file: UploadFile = File(...), threshold: float = Form(0.6)):
312
+ try:
313
+ contents = await file.read()
314
+ image = Image.open(io.BytesIO(contents))
315
+
316
+ predictions_watcher = models["KnochenWächter"](image)
317
+ predictions_master = models["RöntgenMeister"](image)
318
+ predictions_locator = models["KnochenAuge"](image)
319
+
320
+ filtered_preds = [p for p in predictions_locator if p['score'] >= threshold]
321
+ analyzed_image = image
322
+ overall_prediction = "No Fracture"
323
+ max_confidence = 0.0
324
+
325
+ if filtered_preds:
326
+ analyzed_image = draw_boxes(image, filtered_preds)
327
+ overall_prediction = "Fracture Detected"
328
+ max_confidence = max([p['score'] for p in filtered_preds])
329
+
330
+ image_stream = io.BytesIO()
331
+ analyzed_image.save(image_stream, format="PNG")
332
+ image_bytes = image_stream.getvalue()
333
+
334
+ pdf_report = generate_report(name, image_bytes, overall_prediction, max_confidence)
335
+
336
+ headers = {
337
+ 'Content-Disposition': 'attachment; filename="report.pdf"'
338
+ }
339
+ return Response(content=pdf_report, headers=headers, media_type="application/pdf")
340
+
341
+ except Exception as e:
342
+ error_html = f"""
343
+ <!DOCTYPE html>
344
+ <html>
345
+ <head>
346
+ <title>Fehler</title>
347
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
348
+ <style>
349
+ {COMMON_STYLES}
350
+ .error-box {
351
+ background: #fee2e2;
352
+ border: 1px solid #ef4444;
353
+ padding: 20px;
354
+ border-radius: 8px;
355
+ margin: 20px 0;
356
+ }
357
+ </style>
358
+ </head>
359
+ <body>
360
+ <div class="container">
361
+ <div class="error-box">
362
+ <h3>Fehler</h3>
363
+ <p>{str(e)}</p>
364
+ </div>
365
+ <a href="/" class="button back-button">
366
+ ← Zurück
367
+ <div class="button-progress"></div>
368
+ </a>
369
+ </div>
370
+ </body>
371
+ </html>
372
+ """
373
+ return HTMLResponse(content=error_html)
374
+
375
+ if __name__ == "__main__":
376
+ uvicorn.run(app, host="0.0.0.0", port=7860)
377
+
378
+