Spaces:
Sleeping
Sleeping
Update app.py
Browse files
app.py
CHANGED
@@ -15,7 +15,7 @@ from reportlab.lib.units import inch
|
|
15 |
|
16 |
app = FastAPI()
|
17 |
|
18 |
-
#
|
19 |
def load_models():
|
20 |
return {
|
21 |
"KnochenAuge": pipeline("object-detection", model="D3STRON/bone-fracture-detr"),
|
@@ -27,13 +27,14 @@ def load_models():
|
|
27 |
models = load_models()
|
28 |
|
29 |
def translate_label(label):
|
|
|
30 |
translations = {
|
31 |
-
"fracture": "
|
32 |
-
"no fracture": "
|
33 |
"normal": "Normal",
|
34 |
-
"abnormal": "
|
35 |
-
"F1": "
|
36 |
-
"NF": "
|
37 |
}
|
38 |
return translations.get(label.lower(), label)
|
39 |
|
@@ -71,20 +72,17 @@ def draw_boxes(image, predictions):
|
|
71 |
|
72 |
draw = ImageDraw.Draw(result_image)
|
73 |
temp = 36.5 + (score * 2.5)
|
|
|
74 |
label = f"{translate_label(pred['label'])} ({score:.1%} • {temp:.1f}°C)"
|
75 |
|
76 |
-
# Calculate text bounding box more accurately
|
77 |
-
# Temporarily create a dummy draw object to get text size if draw.textbbox is not accurate enough or available for current Pillow version
|
78 |
try:
|
79 |
text_bbox = draw.textbbox((box['xmin'], box['ymin'] - 20), label)
|
80 |
-
except AttributeError:
|
81 |
-
|
82 |
-
|
83 |
-
|
84 |
-
text_height = font_size * 1.2 # rough estimation
|
85 |
text_bbox = (box['xmin'], box['ymin'] - text_height, box['xmin'] + text_width, box['ymin'])
|
86 |
|
87 |
-
|
88 |
draw.rectangle(text_bbox, fill=(0, 0, 0, 180))
|
89 |
|
90 |
draw.text(
|
@@ -185,7 +183,7 @@ async def main():
|
|
185 |
<!DOCTYPE html>
|
186 |
<html>
|
187 |
<head>
|
188 |
-
<title>
|
189 |
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
190 |
<style>
|
191 |
{COMMON_STYLES}
|
@@ -268,20 +266,20 @@ async def main():
|
|
268 |
<div class="upload-section">
|
269 |
<form action="/analyze" method="post" enctype="multipart/form-data" onsubmit="document.getElementById('loading').style.display = 'block';">
|
270 |
<div class="input-field">
|
271 |
-
<label for="patient_name">
|
272 |
<input type="text" id="patient_name" name="patient_name" required>
|
273 |
</div>
|
274 |
<div>
|
275 |
<input type="file" name="file" accept="image/*" required>
|
276 |
</div>
|
277 |
<div class="confidence-slider">
|
278 |
-
<label for="threshold">
|
279 |
<input type="range" id="threshold" name="threshold"
|
280 |
min="0" max="1" step="0.05" value="0.60"
|
281 |
oninput="document.getElementById('thresholdValue').textContent = parseFloat(this.value).toFixed(2)">
|
282 |
</div>
|
283 |
<button type="submit" class="button">
|
284 |
-
|
285 |
<div class="button-progress"></div>
|
286 |
</button>
|
287 |
<div id="loading">Loading...</div>
|
@@ -344,13 +342,13 @@ async def analyze_file(patient_name: str = Form(...), file: UploadFile = File(..
|
|
344 |
|
345 |
story = []
|
346 |
|
347 |
-
story.append(Paragraph("<b>
|
348 |
story.append(Spacer(1, 0.2 * inch))
|
349 |
-
story.append(Paragraph(f"<b>
|
350 |
story.append(Spacer(1, 0.4 * inch))
|
351 |
|
352 |
# KnochenWächter results
|
353 |
-
story.append(Paragraph("<b>KnochenWächter
|
354 |
for pred in predictions_watcher:
|
355 |
story.append(Paragraph(
|
356 |
f"{translate_label(pred['label'])}: {pred['score']:.1%}",
|
@@ -359,7 +357,7 @@ async def analyze_file(patient_name: str = Form(...), file: UploadFile = File(..
|
|
359 |
story.append(Spacer(1, 0.2 * inch))
|
360 |
|
361 |
# RöntgenMeister results
|
362 |
-
story.append(Paragraph("<b>RöntgenMeister
|
363 |
for pred in predictions_master:
|
364 |
story.append(Paragraph(
|
365 |
f"{translate_label(pred['label'])}: {pred['score']:.1%}",
|
@@ -368,63 +366,59 @@ async def analyze_file(patient_name: str = Form(...), file: UploadFile = File(..
|
|
368 |
story.append(Spacer(1, 0.4 * inch))
|
369 |
|
370 |
# Analyzed Image
|
371 |
-
story.append(Paragraph("<b>
|
372 |
|
373 |
-
# Save the result image temporarily to a buffer to be added to PDF
|
374 |
img_buffer = io.BytesIO()
|
375 |
result_image.save(img_buffer, format="PNG")
|
376 |
img_buffer.seek(0)
|
377 |
img_rl = ReportLabImage(img_buffer)
|
378 |
|
379 |
-
# Scale image to fit within page width while maintaining aspect ratio
|
380 |
img_width, img_height = img_rl.drawWidth, img_rl.drawHeight
|
381 |
aspect_ratio = img_height / img_width
|
382 |
-
max_width = 5 * inch
|
383 |
if img_width > max_width:
|
384 |
img_rl.drawWidth = max_width
|
385 |
img_rl.drawHeight = max_width * aspect_ratio
|
386 |
-
|
387 |
-
# Center the image
|
388 |
img_rl.hAlign = 'CENTER'
|
389 |
|
390 |
story.append(img_rl)
|
391 |
story.append(Spacer(1, 0.4 * inch))
|
392 |
|
393 |
-
|
394 |
# Final report text based on object detection
|
395 |
if filtered_preds:
|
396 |
story.append(Paragraph(
|
397 |
-
"<b>
|
398 |
report_text_style
|
399 |
))
|
400 |
for pred in filtered_preds:
|
401 |
score = pred['score']
|
402 |
temp = 36.5 + (score * 2.5)
|
403 |
story.append(Paragraph(
|
404 |
-
f"
|
405 |
report_text_style
|
406 |
))
|
407 |
else:
|
408 |
story.append(Paragraph(
|
409 |
-
"<b>
|
410 |
report_text_style
|
411 |
))
|
412 |
story.append(Spacer(1, 0.2 * inch))
|
413 |
-
story.append(Paragraph("
|
414 |
|
415 |
|
416 |
doc.build(story)
|
417 |
buffer.seek(0)
|
418 |
|
419 |
return StreamingResponse(buffer, media_type="application/pdf",
|
420 |
-
headers={"Content-Disposition": f"attachment; filename=
|
421 |
|
422 |
except Exception as e:
|
423 |
return HTMLResponse(f"""
|
424 |
<!DOCTYPE html>
|
425 |
<html>
|
426 |
<head>
|
427 |
-
<title>
|
428 |
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
429 |
<style>
|
430 |
{COMMON_STYLES}
|
@@ -440,11 +434,11 @@ async def analyze_file(patient_name: str = Form(...), file: UploadFile = File(..
|
|
440 |
<body>
|
441 |
<div class="container">
|
442 |
<div class="error-box">
|
443 |
-
<h3>
|
444 |
<p>{str(e)}</p>
|
445 |
</div>
|
446 |
<a href="/" class="button back-button">
|
447 |
-
←
|
448 |
<div class="button-progress"></div>
|
449 |
</a>
|
450 |
</div>
|
@@ -454,4 +448,3 @@ async def analyze_file(patient_name: str = Form(...), file: UploadFile = File(..
|
|
454 |
|
455 |
if __name__ == "__main__":
|
456 |
uvicorn.run(app, host="0.0.0.0", port=7860)
|
457 |
-
|
|
|
15 |
|
16 |
app = FastAPI()
|
17 |
|
18 |
+
# Load models
|
19 |
def load_models():
|
20 |
return {
|
21 |
"KnochenAuge": pipeline("object-detection", model="D3STRON/bone-fracture-detr"),
|
|
|
27 |
models = load_models()
|
28 |
|
29 |
def translate_label(label):
|
30 |
+
# Keep translations for internal use if needed, but for the PDF we'll use English
|
31 |
translations = {
|
32 |
+
"fracture": "Fracture",
|
33 |
+
"no fracture": "No Fracture",
|
34 |
"normal": "Normal",
|
35 |
+
"abnormal": "Abnormal",
|
36 |
+
"F1": "Fracture", # Assuming F1 also means fracture
|
37 |
+
"NF": "No Fracture" # Assuming NF means no fracture
|
38 |
}
|
39 |
return translations.get(label.lower(), label)
|
40 |
|
|
|
72 |
|
73 |
draw = ImageDraw.Draw(result_image)
|
74 |
temp = 36.5 + (score * 2.5)
|
75 |
+
# Label in English
|
76 |
label = f"{translate_label(pred['label'])} ({score:.1%} • {temp:.1f}°C)"
|
77 |
|
|
|
|
|
78 |
try:
|
79 |
text_bbox = draw.textbbox((box['xmin'], box['ymin'] - 20), label)
|
80 |
+
except AttributeError:
|
81 |
+
font_size = 10
|
82 |
+
text_width = len(label) * font_size * 0.6
|
83 |
+
text_height = font_size * 1.2
|
|
|
84 |
text_bbox = (box['xmin'], box['ymin'] - text_height, box['xmin'] + text_width, box['ymin'])
|
85 |
|
|
|
86 |
draw.rectangle(text_bbox, fill=(0, 0, 0, 180))
|
87 |
|
88 |
draw.text(
|
|
|
183 |
<!DOCTYPE html>
|
184 |
<html>
|
185 |
<head>
|
186 |
+
<title>Fracture Detection</title>
|
187 |
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
188 |
<style>
|
189 |
{COMMON_STYLES}
|
|
|
266 |
<div class="upload-section">
|
267 |
<form action="/analyze" method="post" enctype="multipart/form-data" onsubmit="document.getElementById('loading').style.display = 'block';">
|
268 |
<div class="input-field">
|
269 |
+
<label for="patient_name">Patient Name:</label>
|
270 |
<input type="text" id="patient_name" name="patient_name" required>
|
271 |
</div>
|
272 |
<div>
|
273 |
<input type="file" name="file" accept="image/*" required>
|
274 |
</div>
|
275 |
<div class="confidence-slider">
|
276 |
+
<label for="threshold">Confidence Threshold: <span id="thresholdValue">0.60</span></label>
|
277 |
<input type="range" id="threshold" name="threshold"
|
278 |
min="0" max="1" step="0.05" value="0.60"
|
279 |
oninput="document.getElementById('thresholdValue').textContent = parseFloat(this.value).toFixed(2)">
|
280 |
</div>
|
281 |
<button type="submit" class="button">
|
282 |
+
Analyze & Generate PDF
|
283 |
<div class="button-progress"></div>
|
284 |
</button>
|
285 |
<div id="loading">Loading...</div>
|
|
|
342 |
|
343 |
story = []
|
344 |
|
345 |
+
story.append(Paragraph("<b>Fracture Detection Report</b>", heading_style))
|
346 |
story.append(Spacer(1, 0.2 * inch))
|
347 |
+
story.append(Paragraph(f"<b>Patient Name:</b> {patient_name}", subheading_style))
|
348 |
story.append(Spacer(1, 0.4 * inch))
|
349 |
|
350 |
# KnochenWächter results
|
351 |
+
story.append(Paragraph("<b>KnochenWächter Results:</b>", subheading_style))
|
352 |
for pred in predictions_watcher:
|
353 |
story.append(Paragraph(
|
354 |
f"{translate_label(pred['label'])}: {pred['score']:.1%}",
|
|
|
357 |
story.append(Spacer(1, 0.2 * inch))
|
358 |
|
359 |
# RöntgenMeister results
|
360 |
+
story.append(Paragraph("<b>RöntgenMeister Results:</b>", subheading_style))
|
361 |
for pred in predictions_master:
|
362 |
story.append(Paragraph(
|
363 |
f"{translate_label(pred['label'])}: {pred['score']:.1%}",
|
|
|
366 |
story.append(Spacer(1, 0.4 * inch))
|
367 |
|
368 |
# Analyzed Image
|
369 |
+
story.append(Paragraph("<b>X-ray Image Analysis:</b>", subheading_style))
|
370 |
|
|
|
371 |
img_buffer = io.BytesIO()
|
372 |
result_image.save(img_buffer, format="PNG")
|
373 |
img_buffer.seek(0)
|
374 |
img_rl = ReportLabImage(img_buffer)
|
375 |
|
|
|
376 |
img_width, img_height = img_rl.drawWidth, img_rl.drawHeight
|
377 |
aspect_ratio = img_height / img_width
|
378 |
+
max_width = 5 * inch
|
379 |
if img_width > max_width:
|
380 |
img_rl.drawWidth = max_width
|
381 |
img_rl.drawHeight = max_width * aspect_ratio
|
382 |
+
|
|
|
383 |
img_rl.hAlign = 'CENTER'
|
384 |
|
385 |
story.append(img_rl)
|
386 |
story.append(Spacer(1, 0.4 * inch))
|
387 |
|
|
|
388 |
# Final report text based on object detection
|
389 |
if filtered_preds:
|
390 |
story.append(Paragraph(
|
391 |
+
"<b>The X-ray image analysis shows potential fracture localization.</b>",
|
392 |
report_text_style
|
393 |
))
|
394 |
for pred in filtered_preds:
|
395 |
score = pred['score']
|
396 |
temp = 36.5 + (score * 2.5)
|
397 |
story.append(Paragraph(
|
398 |
+
f"Detection: {translate_label(pred['label'])} with {score:.1%} confidence ({temp:.1f}°C)",
|
399 |
report_text_style
|
400 |
))
|
401 |
else:
|
402 |
story.append(Paragraph(
|
403 |
+
"<b>Based on object localization analysis, no fracture was detected with sufficient confidence.</b>",
|
404 |
report_text_style
|
405 |
))
|
406 |
story.append(Spacer(1, 0.2 * inch))
|
407 |
+
story.append(Paragraph("This is an automatically generated report and should be reviewed by a medical professional.", centered_style))
|
408 |
|
409 |
|
410 |
doc.build(story)
|
411 |
buffer.seek(0)
|
412 |
|
413 |
return StreamingResponse(buffer, media_type="application/pdf",
|
414 |
+
headers={"Content-Disposition": f"attachment; filename=Fracture_Report_{patient_name.replace(' ', '_')}.pdf"})
|
415 |
|
416 |
except Exception as e:
|
417 |
return HTMLResponse(f"""
|
418 |
<!DOCTYPE html>
|
419 |
<html>
|
420 |
<head>
|
421 |
+
<title>Error</title>
|
422 |
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
423 |
<style>
|
424 |
{COMMON_STYLES}
|
|
|
434 |
<body>
|
435 |
<div class="container">
|
436 |
<div class="error-box">
|
437 |
+
<h3>Error</h3>
|
438 |
<p>{str(e)}</p>
|
439 |
</div>
|
440 |
<a href="/" class="button back-button">
|
441 |
+
← Back
|
442 |
<div class="button-progress"></div>
|
443 |
</a>
|
444 |
</div>
|
|
|
448 |
|
449 |
if __name__ == "__main__":
|
450 |
uvicorn.run(app, host="0.0.0.0", port=7860)
|
|