Spaces:
Sleeping
Sleeping
Commit
·
ac76f6e
1
Parent(s):
095c768
Update
Browse files- backend/app.py +69 -23
backend/app.py
CHANGED
|
@@ -569,7 +569,7 @@ async def predict(model_name: str = Form(...), file: UploadFile = File(...)):
|
|
| 569 |
|
| 570 |
# =====================================================
|
| 571 |
|
| 572 |
-
def create_designed_pdf(pdf_path, report_data, analysis_summary_json):
|
| 573 |
doc = SimpleDocTemplate(pdf_path, pagesize=letter,
|
| 574 |
rightMargin=72, leftMargin=72,
|
| 575 |
topMargin=72, bottomMargin=18)
|
|
@@ -665,6 +665,19 @@ def create_designed_pdf(pdf_path, report_data, analysis_summary_json):
|
|
| 665 |
story.append(Paragraph(ai_summary.get('ai_interpretation', 'Not available.'), styles['NormalSmall']))
|
| 666 |
story.append(Spacer(1, 0.2*inch))
|
| 667 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 668 |
# Doctor's Notes
|
| 669 |
story.append(Paragraph("Doctor's Notes", styles['Section']))
|
| 670 |
story.append(Paragraph(report_data.get('doctor_notes') or 'No additional notes provided.', styles['NormalSmall']))
|
|
@@ -677,8 +690,8 @@ def create_designed_pdf(pdf_path, report_data, analysis_summary_json):
|
|
| 677 |
|
| 678 |
# Signatures
|
| 679 |
story.append(Paragraph("Signatures", styles['Section']))
|
| 680 |
-
story.append(Paragraph("
|
| 681 |
-
story.append(Paragraph("
|
| 682 |
story.append(Spacer(1, 0.1*inch))
|
| 683 |
story.append(Paragraph(f"Generated on: {datetime.datetime.now().strftime('%b %d, %Y, %I:%M %p')}", styles['NormalSmall']))
|
| 684 |
|
|
@@ -694,6 +707,7 @@ async def generate_report(
|
|
| 694 |
notes: str = Form(None),
|
| 695 |
analysis_id: str = Form(None),
|
| 696 |
analysis_summary: str = Form(None),
|
|
|
|
| 697 |
):
|
| 698 |
"""Generate a structured medical report from analysis results and metadata."""
|
| 699 |
try:
|
|
@@ -735,23 +749,62 @@ async def generate_report(
|
|
| 735 |
with open(report_json, "w", encoding="utf-8") as f:
|
| 736 |
json.dump(report_data, f, indent=2, ensure_ascii=False)
|
| 737 |
|
| 738 |
-
#
|
|
|
|
| 739 |
pdf_url = None
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 740 |
if REPORTLAB_AVAILABLE:
|
| 741 |
try:
|
| 742 |
pdf_path = os.path.join(report_dir, "report.pdf")
|
| 743 |
-
create_designed_pdf(pdf_path, report_data, analysis_summary)
|
| 744 |
pdf_url = f"/outputs/reports/{report_id}/report.pdf"
|
| 745 |
except Exception as e:
|
| 746 |
print(f"Error creating designed PDF: {e}")
|
| 747 |
pdf_url = None
|
| 748 |
|
| 749 |
-
# Parse analysis_summary to get AI results
|
| 750 |
-
try:
|
| 751 |
-
ai_summary = json.loads(analysis_summary) if analysis_summary else {}
|
| 752 |
-
except (json.JSONDecodeError, TypeError):
|
| 753 |
-
ai_summary = {}
|
| 754 |
-
|
| 755 |
# Determine report type based on analysis summary or model used
|
| 756 |
model_used = ai_summary.get('model_used', '')
|
| 757 |
if 'YOLO' in model_used or 'yolo' in str(analysis_id).lower():
|
|
@@ -809,18 +862,11 @@ async def generate_report(
|
|
| 809 |
json_url = f"/outputs/reports/{report_id}/report.json"
|
| 810 |
html_url = f"/outputs/reports/{report_id}/report.html"
|
| 811 |
|
| 812 |
-
#
|
| 813 |
-
#
|
| 814 |
-
|
| 815 |
-
|
| 816 |
-
|
| 817 |
-
annotated_img_full = ""
|
| 818 |
-
if annotated_img:
|
| 819 |
-
if isinstance(annotated_img, str) and annotated_img.startswith(('http://', 'https://')):
|
| 820 |
-
annotated_img_full = annotated_img
|
| 821 |
-
else:
|
| 822 |
-
# ensure leading slash for relative paths so StaticFiles routing works
|
| 823 |
-
annotated_img_full = annotated_img if str(annotated_img).startswith('/') else '/' + str(annotated_img)
|
| 824 |
|
| 825 |
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 ''
|
| 826 |
|
|
|
|
| 569 |
|
| 570 |
# =====================================================
|
| 571 |
|
| 572 |
+
def create_designed_pdf(pdf_path, report_data, analysis_summary_json, annotated_image_path=None):
|
| 573 |
doc = SimpleDocTemplate(pdf_path, pagesize=letter,
|
| 574 |
rightMargin=72, leftMargin=72,
|
| 575 |
topMargin=72, bottomMargin=18)
|
|
|
|
| 665 |
story.append(Paragraph(ai_summary.get('ai_interpretation', 'Not available.'), styles['NormalSmall']))
|
| 666 |
story.append(Spacer(1, 0.2*inch))
|
| 667 |
|
| 668 |
+
# If an annotated image path was provided and exists on disk, embed it
|
| 669 |
+
if annotated_image_path:
|
| 670 |
+
try:
|
| 671 |
+
if os.path.isfile(annotated_image_path):
|
| 672 |
+
story.append(Spacer(1, 0.1*inch))
|
| 673 |
+
# Use a reasonable width for the embedded image
|
| 674 |
+
img = ReportLabImage(annotated_image_path, width=5*inch, kind='proportional')
|
| 675 |
+
story.append(img)
|
| 676 |
+
story.append(Spacer(1, 0.2*inch))
|
| 677 |
+
except Exception as e:
|
| 678 |
+
# Don't fail the whole PDF creation if image embedding fails
|
| 679 |
+
print(f"⚠️ Could not embed annotated image in PDF: {e}")
|
| 680 |
+
|
| 681 |
# Doctor's Notes
|
| 682 |
story.append(Paragraph("Doctor's Notes", styles['Section']))
|
| 683 |
story.append(Paragraph(report_data.get('doctor_notes') or 'No additional notes provided.', styles['NormalSmall']))
|
|
|
|
| 690 |
|
| 691 |
# Signatures
|
| 692 |
story.append(Paragraph("Signatures", styles['Section']))
|
| 693 |
+
story.append(Paragraph("Rajesh Venugopal, Physician", styles['NormalSmall']))
|
| 694 |
+
#story.append(Paragraph("", styles['NormalSmall']))
|
| 695 |
story.append(Spacer(1, 0.1*inch))
|
| 696 |
story.append(Paragraph(f"Generated on: {datetime.datetime.now().strftime('%b %d, %Y, %I:%M %p')}", styles['NormalSmall']))
|
| 697 |
|
|
|
|
| 707 |
notes: str = Form(None),
|
| 708 |
analysis_id: str = Form(None),
|
| 709 |
analysis_summary: str = Form(None),
|
| 710 |
+
file: UploadFile = File(None),
|
| 711 |
):
|
| 712 |
"""Generate a structured medical report from analysis results and metadata."""
|
| 713 |
try:
|
|
|
|
| 749 |
with open(report_json, "w", encoding="utf-8") as f:
|
| 750 |
json.dump(report_data, f, indent=2, ensure_ascii=False)
|
| 751 |
|
| 752 |
+
# We'll create PDF later (after parsing analysis_summary and resolving
|
| 753 |
+
# any annotated/input image). Initialize pdf_url here.
|
| 754 |
pdf_url = None
|
| 755 |
+
|
| 756 |
+
# Parse analysis_summary to get AI results
|
| 757 |
+
try:
|
| 758 |
+
ai_summary = json.loads(analysis_summary) if analysis_summary else {}
|
| 759 |
+
except (json.JSONDecodeError, TypeError):
|
| 760 |
+
ai_summary = {}
|
| 761 |
+
|
| 762 |
+
# Resolve annotated image: prefer AI/analysis annotated image; if none,
|
| 763 |
+
# save the uploaded input image (if provided) into the report folder
|
| 764 |
+
# and use that as the embedded image.
|
| 765 |
+
annotated_img = ai_summary.get('annotated_image_url') or report_data.get("analysis", {}).get("annotated_image_url") or ""
|
| 766 |
+
annotated_img_full = ""
|
| 767 |
+
annotated_img_local = None
|
| 768 |
+
|
| 769 |
+
if annotated_img:
|
| 770 |
+
# If it's an outputs path served by StaticFiles, map to local file
|
| 771 |
+
if isinstance(annotated_img, str) and annotated_img.startswith('/outputs/'):
|
| 772 |
+
rel = annotated_img[len('/outputs/'):].lstrip('/')
|
| 773 |
+
annotated_img_local = os.path.join(OUTPUT_DIR, rel)
|
| 774 |
+
annotated_img_full = annotated_img
|
| 775 |
+
else:
|
| 776 |
+
# keep absolute URLs as-is for HTML
|
| 777 |
+
annotated_img_full = annotated_img if isinstance(annotated_img, str) else ''
|
| 778 |
+
|
| 779 |
+
# If no annotated image provided, but an input file was uploaded, save it
|
| 780 |
+
if not annotated_img_full and file is not None and getattr(file, 'filename', None):
|
| 781 |
+
try:
|
| 782 |
+
input_filename = f"input_image{os.path.splitext(file.filename)[1] or '.jpg'}"
|
| 783 |
+
input_path = os.path.join(report_dir, input_filename)
|
| 784 |
+
contents = await file.read()
|
| 785 |
+
with open(input_path, 'wb') as out_f:
|
| 786 |
+
out_f.write(contents)
|
| 787 |
+
annotated_img_full = f"/outputs/reports/{report_id}/{input_filename}"
|
| 788 |
+
annotated_img_local = input_path
|
| 789 |
+
except Exception as e:
|
| 790 |
+
print(f"⚠️ Failed to save uploaded input image for report: {e}")
|
| 791 |
+
|
| 792 |
+
# Ensure annotated_img_full has a leading slash if it's a relative path
|
| 793 |
+
if annotated_img_full and not annotated_img_full.startswith(('http://', 'https://')):
|
| 794 |
+
annotated_img_full = annotated_img_full if annotated_img_full.startswith('/') else '/' + annotated_img_full
|
| 795 |
+
|
| 796 |
+
# Now attempt to create the PDF (passing the local annotated image path
|
| 797 |
+
# so the PDF writer can embed it). If annotation is remote or not
|
| 798 |
+
# available, PDF creation will still proceed without the image.
|
| 799 |
if REPORTLAB_AVAILABLE:
|
| 800 |
try:
|
| 801 |
pdf_path = os.path.join(report_dir, "report.pdf")
|
| 802 |
+
create_designed_pdf(pdf_path, report_data, analysis_summary, annotated_img_local)
|
| 803 |
pdf_url = f"/outputs/reports/{report_id}/report.pdf"
|
| 804 |
except Exception as e:
|
| 805 |
print(f"Error creating designed PDF: {e}")
|
| 806 |
pdf_url = None
|
| 807 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 808 |
# Determine report type based on analysis summary or model used
|
| 809 |
model_used = ai_summary.get('model_used', '')
|
| 810 |
if 'YOLO' in model_used or 'yolo' in str(analysis_id).lower():
|
|
|
|
| 862 |
json_url = f"/outputs/reports/{report_id}/report.json"
|
| 863 |
html_url = f"/outputs/reports/{report_id}/report.html"
|
| 864 |
|
| 865 |
+
# annotated_img_full was computed earlier; ensure it's defined and set
|
| 866 |
+
# annotated_img (used by the HTML template conditional) accordingly.
|
| 867 |
+
if 'annotated_img_full' not in locals() or not annotated_img_full:
|
| 868 |
+
annotated_img_full = ''
|
| 869 |
+
annotated_img = annotated_img_full
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 870 |
|
| 871 |
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 ''
|
| 872 |
|