Upload folder using huggingface_hub
Browse files- pdf_helper.py +70 -11
- test_pdf_generation.py +51 -0
pdf_helper.py
CHANGED
|
@@ -121,16 +121,24 @@ from weasyprint import HTML, CSS
|
|
| 121 |
import base64
|
| 122 |
|
| 123 |
def generate_pdf(pdf_path: str, interpretation_html: str, plot_files: list):
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 124 |
if interpretation_html.startswith("```"):
|
| 125 |
interpretation_html = interpretation_html.strip("`").replace("html", "", 1).strip()
|
| 126 |
|
| 127 |
-
#
|
|
|
|
|
|
|
|
|
|
|
|
|
| 128 |
plots_html = "<div style='display:flex; flex-wrap:wrap; gap:20px; margin-top:0;'>"
|
| 129 |
for test_name, filepath in plot_files:
|
| 130 |
with open(filepath, "rb") as f:
|
| 131 |
img_base64 = base64.b64encode(f.read()).decode("utf-8")
|
| 132 |
-
plot_html = f"<img src='data:image/png;base64,{img_base64}' style='width:100%; height:auto;'>"
|
| 133 |
-
# Each plot occupies ~48% so exactly 2 fit per row
|
| 134 |
plots_html += f"<div style='flex:0 0 48%; margin-bottom:20px; text-align:center;'>{plot_html}</div>"
|
| 135 |
plots_html += "</div>"
|
| 136 |
|
|
@@ -146,21 +154,72 @@ def generate_pdf(pdf_path: str, interpretation_html: str, plot_files: list):
|
|
| 146 |
<html>
|
| 147 |
<head>
|
| 148 |
<style>
|
| 149 |
-
|
| 150 |
-
|
| 151 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 152 |
table {{ border-collapse: collapse; width: 100%; margin: 10px 0; }}
|
| 153 |
th, td {{ border: 1px solid #aaa; padding: 6px; text-align: left; }}
|
| 154 |
th {{ background: #eee; }}
|
|
|
|
| 155 |
.disclaimer {{ font-size: 10px; color: red; margin-top: 20px; text-align: center; }}
|
| 156 |
</style>
|
| 157 |
</head>
|
| 158 |
<body>
|
| 159 |
-
|
| 160 |
-
|
| 161 |
-
|
| 162 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 163 |
</body>
|
| 164 |
</html>
|
| 165 |
"""
|
| 166 |
-
|
|
|
|
|
|
| 121 |
import base64
|
| 122 |
|
| 123 |
def generate_pdf(pdf_path: str, interpretation_html: str, plot_files: list):
|
| 124 |
+
import base64
|
| 125 |
+
from weasyprint import HTML
|
| 126 |
+
|
| 127 |
+
logo_path = SheamiConfig.logo_path
|
| 128 |
+
|
| 129 |
if interpretation_html.startswith("```"):
|
| 130 |
interpretation_html = interpretation_html.strip("`").replace("html", "", 1).strip()
|
| 131 |
|
| 132 |
+
# Base64 logo
|
| 133 |
+
with open(logo_path, "rb") as f:
|
| 134 |
+
logo_base64 = base64.b64encode(f.read()).decode("utf-8")
|
| 135 |
+
|
| 136 |
+
# Base64 plots
|
| 137 |
plots_html = "<div style='display:flex; flex-wrap:wrap; gap:20px; margin-top:0;'>"
|
| 138 |
for test_name, filepath in plot_files:
|
| 139 |
with open(filepath, "rb") as f:
|
| 140 |
img_base64 = base64.b64encode(f.read()).decode("utf-8")
|
| 141 |
+
plot_html = f"<img src='data:image/png;base64,{img_base64}' style='width:100%; height:auto; display:block;'>"
|
|
|
|
| 142 |
plots_html += f"<div style='flex:0 0 48%; margin-bottom:20px; text-align:center;'>{plot_html}</div>"
|
| 143 |
plots_html += "</div>"
|
| 144 |
|
|
|
|
| 154 |
<html>
|
| 155 |
<head>
|
| 156 |
<style>
|
| 157 |
+
@page {{
|
| 158 |
+
size: A4;
|
| 159 |
+
margin: 2cm;
|
| 160 |
+
@top-center {{
|
| 161 |
+
content: element(header);
|
| 162 |
+
}}
|
| 163 |
+
@bottom-center {{
|
| 164 |
+
content: "Page " counter(page) " of " counter(pages);
|
| 165 |
+
font-size: 10px;
|
| 166 |
+
color: #555;
|
| 167 |
+
}}
|
| 168 |
+
}}
|
| 169 |
+
|
| 170 |
+
body {{
|
| 171 |
+
font-family: sans-serif;
|
| 172 |
+
margin: 0;
|
| 173 |
+
}}
|
| 174 |
+
|
| 175 |
+
header {{
|
| 176 |
+
position: running(header);
|
| 177 |
+
display: block;
|
| 178 |
+
text-align: center;
|
| 179 |
+
width: 100%;
|
| 180 |
+
height: 100px; /* explicit height ensures image renders */
|
| 181 |
+
line-height: 100px; /* vertical centering */
|
| 182 |
+
overflow: hidden;
|
| 183 |
+
}}
|
| 184 |
+
|
| 185 |
+
header img {{
|
| 186 |
+
max-height: 100%;
|
| 187 |
+
max-width: 100%;
|
| 188 |
+
display: block;
|
| 189 |
+
margin: 0 auto;
|
| 190 |
+
}}
|
| 191 |
+
|
| 192 |
+
.content {{
|
| 193 |
+
margin-top: 120px; /* space for header */
|
| 194 |
+
margin-bottom: 60px; /* space for footer */
|
| 195 |
+
}}
|
| 196 |
+
|
| 197 |
+
h1 {{ text-align: center; margin-bottom:10px; }}
|
| 198 |
+
h2 {{ color:#2c3e50; margin-top:10px; }}
|
| 199 |
+
|
| 200 |
table {{ border-collapse: collapse; width: 100%; margin: 10px 0; }}
|
| 201 |
th, td {{ border: 1px solid #aaa; padding: 6px; text-align: left; }}
|
| 202 |
th {{ background: #eee; }}
|
| 203 |
+
|
| 204 |
.disclaimer {{ font-size: 10px; color: red; margin-top: 20px; text-align: center; }}
|
| 205 |
</style>
|
| 206 |
</head>
|
| 207 |
<body>
|
| 208 |
+
<header>
|
| 209 |
+
<img src='data:image/png;base64,{logo_base64}' alt="Sheami Logo">
|
| 210 |
+
</header>
|
| 211 |
+
|
| 212 |
+
<div class="content">
|
| 213 |
+
{interpretation_html}
|
| 214 |
+
|
| 215 |
+
<h2>Trends</h2>
|
| 216 |
+
{plots_html}
|
| 217 |
+
|
| 218 |
+
<p class="disclaimer">{DISCLAIMER_TEXT}</p>
|
| 219 |
+
</div>
|
| 220 |
+
|
| 221 |
</body>
|
| 222 |
</html>
|
| 223 |
"""
|
| 224 |
+
|
| 225 |
+
HTML(string=full_html, base_url=".").write_pdf(pdf_path)
|
test_pdf_generation.py
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import os
|
| 2 |
+
import tempfile
|
| 3 |
+
import matplotlib.pyplot as plt
|
| 4 |
+
import base64
|
| 5 |
+
from weasyprint import HTML
|
| 6 |
+
|
| 7 |
+
from config import SheamiConfig
|
| 8 |
+
from pdf_helper import generate_pdf
|
| 9 |
+
|
| 10 |
+
def test_generate_pdf():
|
| 11 |
+
from pathlib import Path
|
| 12 |
+
|
| 13 |
+
# Temporary directory for plots
|
| 14 |
+
tmp_dir = tempfile.mkdtemp()
|
| 15 |
+
|
| 16 |
+
# 1. Fake interpretation HTML
|
| 17 |
+
interpretation_html = """
|
| 18 |
+
<h1>Test Patient: John Doe</h1>
|
| 19 |
+
<p>Age: 45, Sex: Male</p>
|
| 20 |
+
<p>Clinical Summary:</p>
|
| 21 |
+
<ul>
|
| 22 |
+
<li>All vitals normal ✅</li>
|
| 23 |
+
<li>Minor deviation in cholesterol ▲</li>
|
| 24 |
+
<li>Vitamin D slightly low ▼</li>
|
| 25 |
+
</ul>
|
| 26 |
+
"""
|
| 27 |
+
|
| 28 |
+
# 2. Generate 4 fake plots
|
| 29 |
+
plot_files = []
|
| 30 |
+
for i in range(4):
|
| 31 |
+
plt.figure(figsize=(4,3))
|
| 32 |
+
plt.plot([1,2,3,4], [i*2+1, i*2+2, i*2+1, i*2+3], marker='o')
|
| 33 |
+
plt.title(f"Test Plot {i+1}")
|
| 34 |
+
plt.xlabel("X")
|
| 35 |
+
plt.ylabel("Y")
|
| 36 |
+
plot_path = os.path.join(tmp_dir, f"plot_{i+1}.png")
|
| 37 |
+
plt.savefig(plot_path)
|
| 38 |
+
plt.close()
|
| 39 |
+
plot_files.append((f"Test {i+1}", plot_path))
|
| 40 |
+
|
| 41 |
+
# 3. Use your SheamiConfig logo path, or fallback to a sample image
|
| 42 |
+
logo_path = SheamiConfig.logo_path if hasattr(SheamiConfig, 'logo_path') else plot_files[0][1]
|
| 43 |
+
|
| 44 |
+
# 4. Call the generate_pdf function
|
| 45 |
+
pdf_path = os.path.join(tmp_dir, "test_report.pdf")
|
| 46 |
+
generate_pdf(pdf_path=pdf_path, interpretation_html=interpretation_html, plot_files=plot_files)
|
| 47 |
+
|
| 48 |
+
print(f"Test PDF generated at: {pdf_path}")
|
| 49 |
+
|
| 50 |
+
|
| 51 |
+
test_generate_pdf()
|