Spaces:
Sleeping
Sleeping
Commit
Β·
bccd6ac
1
Parent(s):
789e997
Update
Browse files- backend/app.py +75 -15
backend/app.py
CHANGED
|
@@ -643,6 +643,10 @@ def create_designed_pdf(pdf_path, report_data, analysis_summary_json, annotated_
|
|
| 643 |
if 'YOLO' in model_used or 'yolo' in str(analysis.get('id', '')).lower():
|
| 644 |
report_type = "CYTOLOGY"
|
| 645 |
report_title = "Cytology Report"
|
|
|
|
|
|
|
|
|
|
|
|
|
| 646 |
elif 'CIN' in model_used or 'cin' in str(analysis.get('id', '')).lower() or 'colpo' in str(analysis.get('id', '')).lower():
|
| 647 |
report_type = "COLPOSCOPY"
|
| 648 |
report_title = "Colposcopy Report"
|
|
@@ -694,11 +698,17 @@ def create_designed_pdf(pdf_path, report_data, analysis_summary_json, annotated_
|
|
| 694 |
story.append(Paragraph(f"<b>Benign Confidence:</b> {benign_conf:.2f}%", styles['NormalSmall']))
|
| 695 |
story.append(Paragraph(f"<b>Malignant Confidence:</b> {malignant_conf:.2f}%", styles['NormalSmall']))
|
| 696 |
elif report_type == "CYTOLOGY":
|
| 697 |
-
# For cytology
|
| 698 |
-
|
| 699 |
-
|
| 700 |
-
|
| 701 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 702 |
else:
|
| 703 |
# For CIN/Colposcopy, show class confidences
|
| 704 |
confidence_dict = ai_summary.get('confidence', {})
|
|
@@ -717,8 +727,19 @@ def create_designed_pdf(pdf_path, report_data, analysis_summary_json, annotated_
|
|
| 717 |
try:
|
| 718 |
if os.path.isfile(annotated_image_path):
|
| 719 |
story.append(Spacer(1, 0.1*inch))
|
| 720 |
-
#
|
| 721 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 722 |
story.append(img)
|
| 723 |
story.append(Spacer(1, 0.2*inch))
|
| 724 |
except Exception as e:
|
|
@@ -812,6 +833,7 @@ async def generate_report(
|
|
| 812 |
annotated_img = ai_summary.get('annotated_image_url') or report_data.get("analysis", {}).get("annotated_image_url") or ""
|
| 813 |
annotated_img_full = ""
|
| 814 |
annotated_img_local = None
|
|
|
|
| 815 |
|
| 816 |
if annotated_img:
|
| 817 |
# If it's an outputs path served by StaticFiles, map to local file
|
|
@@ -836,6 +858,26 @@ async def generate_report(
|
|
| 836 |
except Exception as e:
|
| 837 |
print(f"β οΈ Failed to save uploaded input image for report: {e}")
|
| 838 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 839 |
# Ensure annotated_img_full has a leading slash if it's a relative path
|
| 840 |
if annotated_img_full and not annotated_img_full.startswith(('http://', 'https://')):
|
| 841 |
annotated_img_full = annotated_img_full if annotated_img_full.startswith('/') else '/' + annotated_img_full
|
|
@@ -857,6 +899,10 @@ async def generate_report(
|
|
| 857 |
if 'YOLO' in model_used or 'yolo' in str(analysis_id).lower():
|
| 858 |
report_type = "Cytology"
|
| 859 |
report_title = "Cytology Report"
|
|
|
|
|
|
|
|
|
|
|
|
|
| 860 |
elif 'CIN' in model_used or 'cin' in str(analysis_id).lower() or 'colpo' in str(analysis_id).lower():
|
| 861 |
report_type = "Colposcopy"
|
| 862 |
report_title = "Colposcopy Report"
|
|
@@ -881,12 +927,23 @@ async def generate_report(
|
|
| 881 |
<tr><th>Malignant Confidence</th><td>{malignant_conf:.2f}%</td></tr>
|
| 882 |
"""
|
| 883 |
elif report_type == "Cytology":
|
| 884 |
-
# For cytology (YOLO), show abnormal/normal
|
| 885 |
-
|
| 886 |
-
|
| 887 |
-
|
| 888 |
-
|
| 889 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 890 |
else:
|
| 891 |
# For CIN/Colposcopy or other models, show generic confidence
|
| 892 |
confidence_dict = ai_summary.get('confidence', {})
|
|
@@ -912,6 +969,9 @@ async def generate_report(
|
|
| 912 |
annotated_img_full = ''
|
| 913 |
annotated_img = annotated_img_full
|
| 914 |
|
|
|
|
|
|
|
|
|
|
| 915 |
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 ''
|
| 916 |
|
| 917 |
# Format generated time
|
|
@@ -922,7 +982,7 @@ async def generate_report(
|
|
| 922 |
<head>
|
| 923 |
<meta charset="utf-8" />
|
| 924 |
<meta name="viewport" content="width=device-width,initial-scale=1" />
|
| 925 |
-
|
| 926 |
<style>
|
| 927 |
:root{{--bg:#f8fafc;--card:#ffffff;--muted:#6b7280;--accent:#0f172a}}
|
| 928 |
body{{font-family:Inter,ui-sans-serif,system-ui,-apple-system,"Segoe UI",Roboto,"Helvetica Neue",Arial;margin:0;background:var(--bg);color:var(--accent);line-height:1.45}}
|
|
@@ -1009,7 +1069,7 @@ async def generate_report(
|
|
| 1009 |
</div>
|
| 1010 |
</div>
|
| 1011 |
|
| 1012 |
-
|
| 1013 |
|
| 1014 |
<div class="full">
|
| 1015 |
<div class="section-title">Doctor\'s Notes</div>
|
|
|
|
| 643 |
if 'YOLO' in model_used or 'yolo' in str(analysis.get('id', '')).lower():
|
| 644 |
report_type = "CYTOLOGY"
|
| 645 |
report_title = "Cytology Report"
|
| 646 |
+
elif 'MWT' in model_used or 'mwt' in str(model_used).lower():
|
| 647 |
+
# MWT is a cytology classifier; use a clearer report title for MWT results
|
| 648 |
+
report_type = "CYTOLOGY"
|
| 649 |
+
report_title = "Cytology Analysis Report"
|
| 650 |
elif 'CIN' in model_used or 'cin' in str(analysis.get('id', '')).lower() or 'colpo' in str(analysis.get('id', '')).lower():
|
| 651 |
report_type = "COLPOSCOPY"
|
| 652 |
report_title = "Colposcopy Report"
|
|
|
|
| 698 |
story.append(Paragraph(f"<b>Benign Confidence:</b> {benign_conf:.2f}%", styles['NormalSmall']))
|
| 699 |
story.append(Paragraph(f"<b>Malignant Confidence:</b> {malignant_conf:.2f}%", styles['NormalSmall']))
|
| 700 |
elif report_type == "CYTOLOGY":
|
| 701 |
+
# For cytology and MWT, show class confidences if available, otherwise show abnormal/normal cells
|
| 702 |
+
confidence_dict = ai_summary.get('confidence', {})
|
| 703 |
+
if isinstance(confidence_dict, dict) and confidence_dict:
|
| 704 |
+
for cls, val in confidence_dict.items():
|
| 705 |
+
conf_pct = val * 100 if isinstance(val, (int, float)) else 0
|
| 706 |
+
story.append(Paragraph(f"<b>{cls} Confidence:</b> {conf_pct:.2f}%", styles['NormalSmall']))
|
| 707 |
+
else:
|
| 708 |
+
if 'abnormal_cells' in ai_summary:
|
| 709 |
+
story.append(Paragraph(f"<b>Abnormal Cells:</b> {ai_summary.get('abnormal_cells', 'N/A')}", styles['NormalSmall']))
|
| 710 |
+
if 'normal_cells' in ai_summary:
|
| 711 |
+
story.append(Paragraph(f"<b>Normal Cells:</b> {ai_summary.get('normal_cells', 'N/A')}", styles['NormalSmall']))
|
| 712 |
else:
|
| 713 |
# For CIN/Colposcopy, show class confidences
|
| 714 |
confidence_dict = ai_summary.get('confidence', {})
|
|
|
|
| 727 |
try:
|
| 728 |
if os.path.isfile(annotated_image_path):
|
| 729 |
story.append(Spacer(1, 0.1*inch))
|
| 730 |
+
# Determine image pixel size and scale to a reasonable width for PDF
|
| 731 |
+
try:
|
| 732 |
+
from PIL import Image as PILImageLocal
|
| 733 |
+
with PILImageLocal.open(annotated_image_path) as im:
|
| 734 |
+
img_w, img_h = im.size
|
| 735 |
+
except Exception:
|
| 736 |
+
img_w, img_h = (800, 600)
|
| 737 |
+
|
| 738 |
+
# Display width in points (ReportLab uses points; 1 inch = 72 points). Assume 96 DPI for pixel->inch.
|
| 739 |
+
display_width_px = max(300, min(img_w, 800))
|
| 740 |
+
width_points = min(5 * inch, (display_width_px / 96.0) * inch)
|
| 741 |
+
|
| 742 |
+
img = ReportLabImage(annotated_image_path, width=width_points, kind='proportional')
|
| 743 |
story.append(img)
|
| 744 |
story.append(Spacer(1, 0.2*inch))
|
| 745 |
except Exception as e:
|
|
|
|
| 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 |
+
annotated_img_display_width = None
|
| 837 |
|
| 838 |
if annotated_img:
|
| 839 |
# If it's an outputs path served by StaticFiles, map to local file
|
|
|
|
| 858 |
except Exception as e:
|
| 859 |
print(f"β οΈ Failed to save uploaded input image for report: {e}")
|
| 860 |
|
| 861 |
+
# If we have a local image file, compute a reasonable display width (px)
|
| 862 |
+
try:
|
| 863 |
+
if annotated_img_local and os.path.isfile(annotated_img_local):
|
| 864 |
+
from PIL import Image as PILImageLocal
|
| 865 |
+
with PILImageLocal.open(annotated_img_local) as im:
|
| 866 |
+
img_w, img_h = im.size
|
| 867 |
+
# Choose display width: cap at 800px, don't go below 300px for visibility
|
| 868 |
+
annotated_img_display_width = max(300, min(img_w, 800))
|
| 869 |
+
elif annotated_img_full and annotated_img_full.startswith('/outputs/'):
|
| 870 |
+
# Map to local and attempt to open
|
| 871 |
+
rel = annotated_img_full[len('/outputs/'):].lstrip('/')
|
| 872 |
+
candidate = os.path.join(OUTPUT_DIR, rel)
|
| 873 |
+
if os.path.isfile(candidate):
|
| 874 |
+
from PIL import Image as PILImageLocal
|
| 875 |
+
with PILImageLocal.open(candidate) as im:
|
| 876 |
+
img_w, img_h = im.size
|
| 877 |
+
annotated_img_display_width = max(300, min(img_w, 800))
|
| 878 |
+
except Exception:
|
| 879 |
+
annotated_img_display_width = None
|
| 880 |
+
|
| 881 |
# Ensure annotated_img_full has a leading slash if it's a relative path
|
| 882 |
if annotated_img_full and not annotated_img_full.startswith(('http://', 'https://')):
|
| 883 |
annotated_img_full = annotated_img_full if annotated_img_full.startswith('/') else '/' + annotated_img_full
|
|
|
|
| 899 |
if 'YOLO' in model_used or 'yolo' in str(analysis_id).lower():
|
| 900 |
report_type = "Cytology"
|
| 901 |
report_title = "Cytology Report"
|
| 902 |
+
elif 'MWT' in model_used or 'mwt' in str(model_used).lower() or 'mwt' in str(analysis_id).lower():
|
| 903 |
+
# MWT is a cytology classifier β use clearer title
|
| 904 |
+
report_type = "Cytology"
|
| 905 |
+
report_title = "Cytology Analysis Report"
|
| 906 |
elif 'CIN' in model_used or 'cin' in str(analysis_id).lower() or 'colpo' in str(analysis_id).lower():
|
| 907 |
report_type = "Colposcopy"
|
| 908 |
report_title = "Colposcopy Report"
|
|
|
|
| 927 |
<tr><th>Malignant Confidence</th><td>{malignant_conf:.2f}%</td></tr>
|
| 928 |
"""
|
| 929 |
elif report_type == "Cytology":
|
| 930 |
+
# For cytology (YOLO) or MWT, show class confidences if provided, else abnormal/normal counts
|
| 931 |
+
confidence_dict = ai_summary.get('confidence', {})
|
| 932 |
+
if isinstance(confidence_dict, dict) and confidence_dict:
|
| 933 |
+
confidence_rows = ""
|
| 934 |
+
for cls, val in confidence_dict.items():
|
| 935 |
+
conf_pct = val * 100 if isinstance(val, (int, float)) else 0
|
| 936 |
+
confidence_rows += f"<tr><th>{cls} Confidence</th><td>{conf_pct:.2f}%</td></tr>\n "
|
| 937 |
+
analysis_metrics_html = f"""
|
| 938 |
+
<tr><th>System</th><td>Manalife AI System β Automated Analysis</td></tr>
|
| 939 |
+
{confidence_rows}
|
| 940 |
+
"""
|
| 941 |
+
else:
|
| 942 |
+
analysis_metrics_html = f"""
|
| 943 |
+
<tr><th>System</th><td>Manalife AI System β Automated Analysis</td></tr>
|
| 944 |
+
<tr><th>Abnormal Cells</th><td>{ai_summary.get('abnormal_cells', 'N/A')}</td></tr>
|
| 945 |
+
<tr><th>Normal Cells</th><td>{ai_summary.get('normal_cells', 'N/A')}</td></tr>
|
| 946 |
+
"""
|
| 947 |
else:
|
| 948 |
# For CIN/Colposcopy or other models, show generic confidence
|
| 949 |
confidence_dict = ai_summary.get('confidence', {})
|
|
|
|
| 969 |
annotated_img_full = ''
|
| 970 |
annotated_img = annotated_img_full
|
| 971 |
|
| 972 |
+
# Prepare width attribute for the HTML img tag if available
|
| 973 |
+
annotated_img_width_attr = f' width="{annotated_img_display_width}"' if annotated_img_display_width else ''
|
| 974 |
+
|
| 975 |
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 ''
|
| 976 |
|
| 977 |
# Format generated time
|
|
|
|
| 982 |
<head>
|
| 983 |
<meta charset="utf-8" />
|
| 984 |
<meta name="viewport" content="width=device-width,initial-scale=1" />
|
| 985 |
+
<title>{report_title} β Manalife AI</title>
|
| 986 |
<style>
|
| 987 |
:root{{--bg:#f8fafc;--card:#ffffff;--muted:#6b7280;--accent:#0f172a}}
|
| 988 |
body{{font-family:Inter,ui-sans-serif,system-ui,-apple-system,"Segoe UI",Roboto,"Helvetica Neue",Arial;margin:0;background:var(--bg);color:var(--accent);line-height:1.45}}
|
|
|
|
| 1069 |
</div>
|
| 1070 |
</div>
|
| 1071 |
|
| 1072 |
+
{'<div class="full"><div class="section-title">Annotated Analysis Image</div><img src="' + annotated_img_full + '" class="annotated-image" alt="Annotated Analysis Result"' + annotated_img_width_attr + ' /></div>' if annotated_img else ''}
|
| 1073 |
|
| 1074 |
<div class="full">
|
| 1075 |
<div class="section-title">Doctor\'s Notes</div>
|