Spaces:
Running
Running
Commit
·
8bf4cb9
1
Parent(s):
bda7ce7
Update
Browse files- backend/app.py +32 -40
backend/app.py
CHANGED
|
@@ -488,27 +488,12 @@ async def predict(model_name: str = Form(...), file: UploadFile = File(...)):
|
|
| 488 |
# Generate a brief AI interpretation using the Mistral client (if available)
|
| 489 |
ai_interp = generate_mwt_summary(predicted_label, confidences, avg_confidence)
|
| 490 |
|
| 491 |
-
# Save the input image to outputs/images so reports can embed it when no
|
| 492 |
-
# annotated image is produced by the model (MWT is a classifier only).
|
| 493 |
-
try:
|
| 494 |
-
os.makedirs(IMAGES_DIR, exist_ok=True)
|
| 495 |
-
input_filename = f"input_{uuid.uuid4().hex[:8]}.jpg"
|
| 496 |
-
input_path = os.path.join(IMAGES_DIR, input_filename)
|
| 497 |
-
# 'contents' was read earlier from the uploaded file
|
| 498 |
-
with open(input_path, 'wb') as out_f:
|
| 499 |
-
out_f.write(contents)
|
| 500 |
-
input_image_url = f"/outputs/images/{input_filename}"
|
| 501 |
-
except Exception as e:
|
| 502 |
-
print(f"⚠️ Failed to save input image for MWT report embedding: {e}")
|
| 503 |
-
input_image_url = ""
|
| 504 |
-
|
| 505 |
return {
|
| 506 |
"model_used": "MWT Classifier",
|
| 507 |
"prediction": predicted_label,
|
| 508 |
"confidence": confidences,
|
| 509 |
"summary": {
|
| 510 |
"ai_interpretation": ai_interp,
|
| 511 |
-
"annotated_image_url": input_image_url,
|
| 512 |
},
|
| 513 |
}
|
| 514 |
|
|
@@ -848,7 +833,6 @@ async def generate_report(
|
|
| 848 |
annotated_img = ai_summary.get('annotated_image_url') or report_data.get("analysis", {}).get("annotated_image_url") or ""
|
| 849 |
annotated_img_full = ""
|
| 850 |
annotated_img_local = None
|
| 851 |
-
annotated_img_display_width = None
|
| 852 |
|
| 853 |
if annotated_img:
|
| 854 |
# If it's an outputs path served by StaticFiles, map to local file
|
|
@@ -873,30 +857,33 @@ async def generate_report(
|
|
| 873 |
except Exception as e:
|
| 874 |
print(f"⚠️ Failed to save uploaded input image for report: {e}")
|
| 875 |
|
| 876 |
-
# If we have a local image file, compute a reasonable display width (px)
|
| 877 |
-
try:
|
| 878 |
-
if annotated_img_local and os.path.isfile(annotated_img_local):
|
| 879 |
-
from PIL import Image as PILImageLocal
|
| 880 |
-
with PILImageLocal.open(annotated_img_local) as im:
|
| 881 |
-
img_w, img_h = im.size
|
| 882 |
-
# Choose display width: cap at 800px, don't go below 300px for visibility
|
| 883 |
-
annotated_img_display_width = max(300, min(img_w, 800))
|
| 884 |
-
elif annotated_img_full and annotated_img_full.startswith('/outputs/'):
|
| 885 |
-
# Map to local and attempt to open
|
| 886 |
-
rel = annotated_img_full[len('/outputs/'):].lstrip('/')
|
| 887 |
-
candidate = os.path.join(OUTPUT_DIR, rel)
|
| 888 |
-
if os.path.isfile(candidate):
|
| 889 |
-
from PIL import Image as PILImageLocal
|
| 890 |
-
with PILImageLocal.open(candidate) as im:
|
| 891 |
-
img_w, img_h = im.size
|
| 892 |
-
annotated_img_display_width = max(300, min(img_w, 800))
|
| 893 |
-
except Exception:
|
| 894 |
-
annotated_img_display_width = None
|
| 895 |
-
|
| 896 |
# Ensure annotated_img_full has a leading slash if it's a relative path
|
| 897 |
if annotated_img_full and not annotated_img_full.startswith(('http://', 'https://')):
|
| 898 |
annotated_img_full = annotated_img_full if annotated_img_full.startswith('/') else '/' + annotated_img_full
|
| 899 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 900 |
# Now attempt to create the PDF (passing the local annotated image path
|
| 901 |
# so the PDF writer can embed it). If annotation is remote or not
|
| 902 |
# available, PDF creation will still proceed without the image.
|
|
@@ -984,9 +971,6 @@ async def generate_report(
|
|
| 984 |
annotated_img_full = ''
|
| 985 |
annotated_img = annotated_img_full
|
| 986 |
|
| 987 |
-
# Prepare width attribute for the HTML img tag if available
|
| 988 |
-
annotated_img_width_attr = f' width="{annotated_img_display_width}"' if annotated_img_display_width else ''
|
| 989 |
-
|
| 990 |
download_pdf_btn = f'<a href="{pdf_url}" download style="text-decoration:none"><button class="btn-secondary">Download PDF</button></a>' if pdf_url else ''
|
| 991 |
|
| 992 |
# Format generated time
|
|
@@ -1084,7 +1068,7 @@ async def generate_report(
|
|
| 1084 |
</div>
|
| 1085 |
</div>
|
| 1086 |
|
| 1087 |
-
|
| 1088 |
|
| 1089 |
<div class="full">
|
| 1090 |
<div class="section-title">Doctor\'s Notes</div>
|
|
@@ -1124,6 +1108,14 @@ async def generate_report(
|
|
| 1124 |
with open(report_html, "w", encoding="utf-8") as f:
|
| 1125 |
f.write(html_content)
|
| 1126 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1127 |
return {
|
| 1128 |
"report_id": report_id,
|
| 1129 |
"json_url": json_url,
|
|
|
|
| 488 |
# Generate a brief AI interpretation using the Mistral client (if available)
|
| 489 |
ai_interp = generate_mwt_summary(predicted_label, confidences, avg_confidence)
|
| 490 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 491 |
return {
|
| 492 |
"model_used": "MWT Classifier",
|
| 493 |
"prediction": predicted_label,
|
| 494 |
"confidence": confidences,
|
| 495 |
"summary": {
|
| 496 |
"ai_interpretation": ai_interp,
|
|
|
|
| 497 |
},
|
| 498 |
}
|
| 499 |
|
|
|
|
| 833 |
annotated_img = ai_summary.get('annotated_image_url') or report_data.get("analysis", {}).get("annotated_image_url") or ""
|
| 834 |
annotated_img_full = ""
|
| 835 |
annotated_img_local = None
|
|
|
|
| 836 |
|
| 837 |
if annotated_img:
|
| 838 |
# If it's an outputs path served by StaticFiles, map to local file
|
|
|
|
| 857 |
except Exception as e:
|
| 858 |
print(f"⚠️ Failed to save uploaded input image for report: {e}")
|
| 859 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 860 |
# Ensure annotated_img_full has a leading slash if it's a relative path
|
| 861 |
if annotated_img_full and not annotated_img_full.startswith(('http://', 'https://')):
|
| 862 |
annotated_img_full = annotated_img_full if annotated_img_full.startswith('/') else '/' + annotated_img_full
|
| 863 |
|
| 864 |
+
# If we have a local annotated image but it's stored in the shared images folder
|
| 865 |
+
# (e.g. /outputs/images/...), copy it into this report's folder so the HTML/PDF
|
| 866 |
+
# can reference the image relative to the report directory. This also makes the
|
| 867 |
+
# image visible when opening report.html directly from disk (file://).
|
| 868 |
+
try:
|
| 869 |
+
if annotated_img_local:
|
| 870 |
+
annotated_img_local_abs = os.path.abspath(annotated_img_local)
|
| 871 |
+
report_dir_abs = os.path.abspath(report_dir)
|
| 872 |
+
# If the image is not already in the report directory, copy it there
|
| 873 |
+
if not os.path.commonpath([annotated_img_local_abs, report_dir_abs]) == report_dir_abs:
|
| 874 |
+
src_basename = os.path.basename(annotated_img_local_abs)
|
| 875 |
+
dest_name = f"annotated_{src_basename}"
|
| 876 |
+
dest_path = os.path.join(report_dir, dest_name)
|
| 877 |
+
try:
|
| 878 |
+
shutil.copy2(annotated_img_local_abs, dest_path)
|
| 879 |
+
annotated_img_local = dest_path
|
| 880 |
+
annotated_img_full = f"/outputs/reports/{report_id}/{dest_name}"
|
| 881 |
+
except Exception as e:
|
| 882 |
+
# If copy fails, keep using the original annotated_img_full (may be served by StaticFiles)
|
| 883 |
+
print(f"⚠️ Failed to copy annotated image into report folder: {e}")
|
| 884 |
+
except Exception:
|
| 885 |
+
pass
|
| 886 |
+
|
| 887 |
# Now attempt to create the PDF (passing the local annotated image path
|
| 888 |
# so the PDF writer can embed it). If annotation is remote or not
|
| 889 |
# available, PDF creation will still proceed without the image.
|
|
|
|
| 971 |
annotated_img_full = ''
|
| 972 |
annotated_img = annotated_img_full
|
| 973 |
|
|
|
|
|
|
|
|
|
|
| 974 |
download_pdf_btn = f'<a href="{pdf_url}" download style="text-decoration:none"><button class="btn-secondary">Download PDF</button></a>' if pdf_url else ''
|
| 975 |
|
| 976 |
# Format generated time
|
|
|
|
| 1068 |
</div>
|
| 1069 |
</div>
|
| 1070 |
|
| 1071 |
+
{'<div class="full"><div class="section-title">Annotated Analysis Image</div><img src="' + annotated_img_full + '" class="annotated-image" alt="Annotated Analysis Result" /></div>' if annotated_img else ''}
|
| 1072 |
|
| 1073 |
<div class="full">
|
| 1074 |
<div class="section-title">Doctor\'s Notes</div>
|
|
|
|
| 1108 |
with open(report_html, "w", encoding="utf-8") as f:
|
| 1109 |
f.write(html_content)
|
| 1110 |
|
| 1111 |
+
# Update report.json to include the resolved annotated image url so callers can find it
|
| 1112 |
+
try:
|
| 1113 |
+
report_data['analysis']['annotated_image_url'] = annotated_img_full or ''
|
| 1114 |
+
with open(report_json, 'w', encoding='utf-8') as f:
|
| 1115 |
+
json.dump(report_data, f, indent=2, ensure_ascii=False)
|
| 1116 |
+
except Exception as e:
|
| 1117 |
+
print(f"⚠️ Failed to update report.json with annotated image url: {e}")
|
| 1118 |
+
|
| 1119 |
return {
|
| 1120 |
"report_id": report_id,
|
| 1121 |
"json_url": json_url,
|