Spaces:
Running on Zero
Running on Zero
Render uploaded document preview
Browse files
app.py
CHANGED
|
@@ -4,6 +4,9 @@ import os
|
|
| 4 |
import re
|
| 5 |
import time
|
| 6 |
import traceback
|
|
|
|
|
|
|
|
|
|
| 7 |
from html import escape
|
| 8 |
from typing import Any
|
| 9 |
|
|
@@ -187,41 +190,68 @@ def upload_state(uploaded_file: str | None) -> tuple[Any, Any]:
|
|
| 187 |
)
|
| 188 |
|
| 189 |
filename = os.path.basename(uploaded_file)
|
|
|
|
| 190 |
return (
|
| 191 |
gr.update(visible=False),
|
| 192 |
-
gr.update(visible=True, value=selected_document_html(filename)),
|
| 193 |
workflow_phase_html("processing"),
|
| 194 |
)
|
| 195 |
|
| 196 |
|
| 197 |
-
def selected_document_html(filename: str | None = None) -> str:
|
| 198 |
if not filename:
|
| 199 |
filename = "Document ready"
|
| 200 |
-
|
| 201 |
-
|
| 202 |
-
|
| 203 |
-
|
|
|
|
| 204 |
<div class="bte-selected-preview-header">
|
| 205 |
<span></span><span></span><span></span>
|
| 206 |
</div>
|
| 207 |
-
<div class="bte-selected-preview-body">
|
| 208 |
-
<div class="bte-preview-line bte-preview-line--title"></div>
|
| 209 |
-
<div class="bte-preview-line bte-preview-line--lg"></div>
|
| 210 |
-
<div class="bte-preview-line bte-preview-line--md"></div>
|
| 211 |
-
<div class="bte-preview-line bte-preview-line--sm"></div>
|
| 212 |
-
<div class="bte-preview-grid">
|
| 213 |
-
<span></span><span></span><span></span>
|
| 214 |
-
<span></span><span></span><span></span>
|
| 215 |
-
</div>
|
| 216 |
-
<div class="bte-preview-watermark">{escape(filename)}</div>
|
| 217 |
-
</div>
|
| 218 |
</div>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 219 |
<div class="bte-selected-preview-overlay"></div>
|
|
|
|
| 220 |
</div>
|
| 221 |
</section>
|
| 222 |
"""
|
| 223 |
|
| 224 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 225 |
def empty_report_html(
|
| 226 |
title: str = "Report preview",
|
| 227 |
detail: str = "Extracted lab markers will render here as an interactive medical document.",
|
|
@@ -1749,10 +1779,23 @@ gradio-app,
|
|
| 1749 |
overflow: hidden;
|
| 1750 |
}
|
| 1751 |
|
| 1752 |
-
.bte-
|
|
|
|
| 1753 |
position: absolute;
|
| 1754 |
inset: 18px;
|
| 1755 |
border-radius: 20px;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1756 |
background:
|
| 1757 |
linear-gradient(180deg, rgba(255, 255, 255, 0.97), rgba(245, 248, 252, 0.96));
|
| 1758 |
border: 1px solid rgba(217, 226, 238, 0.82);
|
|
@@ -1819,7 +1862,10 @@ gradio-app,
|
|
| 1819 |
}
|
| 1820 |
|
| 1821 |
.bte-preview-watermark {
|
| 1822 |
-
|
|
|
|
|
|
|
|
|
|
| 1823 |
color: rgba(17, 24, 39, 0.2);
|
| 1824 |
font-size: 18px;
|
| 1825 |
font-weight: 700;
|
|
|
|
| 4 |
import re
|
| 5 |
import time
|
| 6 |
import traceback
|
| 7 |
+
import base64
|
| 8 |
+
import io
|
| 9 |
+
from pathlib import Path
|
| 10 |
from html import escape
|
| 11 |
from typing import Any
|
| 12 |
|
|
|
|
| 190 |
)
|
| 191 |
|
| 192 |
filename = os.path.basename(uploaded_file)
|
| 193 |
+
preview_data_url = _uploaded_file_preview_data_url(uploaded_file)
|
| 194 |
return (
|
| 195 |
gr.update(visible=False),
|
| 196 |
+
gr.update(visible=True, value=selected_document_html(filename, preview_data_url)),
|
| 197 |
workflow_phase_html("processing"),
|
| 198 |
)
|
| 199 |
|
| 200 |
|
| 201 |
+
def selected_document_html(filename: str | None = None, preview_data_url: str | None = None) -> str:
|
| 202 |
if not filename:
|
| 203 |
filename = "Document ready"
|
| 204 |
+
preview_markup = (
|
| 205 |
+
f'<img class="bte-upload-preview-image" src="{escape(preview_data_url)}" alt="Uploaded document preview">'
|
| 206 |
+
if preview_data_url
|
| 207 |
+
else """
|
| 208 |
+
<div class="bte-upload-preview-placeholder">
|
| 209 |
<div class="bte-selected-preview-header">
|
| 210 |
<span></span><span></span><span></span>
|
| 211 |
</div>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 212 |
</div>
|
| 213 |
+
"""
|
| 214 |
+
)
|
| 215 |
+
return f"""
|
| 216 |
+
<section class="bte-selected-document">
|
| 217 |
+
<div class="bte-selected-preview" aria-hidden="true">
|
| 218 |
+
{preview_markup}
|
| 219 |
<div class="bte-selected-preview-overlay"></div>
|
| 220 |
+
<div class="bte-preview-watermark">{escape(filename)}</div>
|
| 221 |
</div>
|
| 222 |
</section>
|
| 223 |
"""
|
| 224 |
|
| 225 |
|
| 226 |
+
def _uploaded_file_preview_data_url(uploaded_file: str) -> str | None:
|
| 227 |
+
path = Path(uploaded_file)
|
| 228 |
+
suffix = path.suffix.lower()
|
| 229 |
+
|
| 230 |
+
if suffix in {".png", ".jpg", ".jpeg", ".webp", ".tif", ".tiff"}:
|
| 231 |
+
from PIL import Image, ImageOps
|
| 232 |
+
|
| 233 |
+
with Image.open(path) as image:
|
| 234 |
+
image = ImageOps.exif_transpose(image).convert("RGB")
|
| 235 |
+
image.thumbnail((1200, 1200))
|
| 236 |
+
buffer = io.BytesIO()
|
| 237 |
+
image.save(buffer, format="JPEG", quality=86, optimize=True)
|
| 238 |
+
encoded = base64.b64encode(buffer.getvalue()).decode("ascii")
|
| 239 |
+
return f"data:image/jpeg;base64,{encoded}"
|
| 240 |
+
|
| 241 |
+
if suffix == ".pdf":
|
| 242 |
+
import fitz
|
| 243 |
+
|
| 244 |
+
with fitz.open(path) as document:
|
| 245 |
+
if document.page_count == 0:
|
| 246 |
+
return None
|
| 247 |
+
page = document.load_page(0)
|
| 248 |
+
pixmap = page.get_pixmap(matrix=fitz.Matrix(2, 2), alpha=False)
|
| 249 |
+
encoded = base64.b64encode(pixmap.tobytes("png")).decode("ascii")
|
| 250 |
+
return f"data:image/png;base64,{encoded}"
|
| 251 |
+
|
| 252 |
+
return None
|
| 253 |
+
|
| 254 |
+
|
| 255 |
def empty_report_html(
|
| 256 |
title: str = "Report preview",
|
| 257 |
detail: str = "Extracted lab markers will render here as an interactive medical document.",
|
|
|
|
| 1779 |
overflow: hidden;
|
| 1780 |
}
|
| 1781 |
|
| 1782 |
+
.bte-upload-preview-image,
|
| 1783 |
+
.bte-upload-preview-placeholder {
|
| 1784 |
position: absolute;
|
| 1785 |
inset: 18px;
|
| 1786 |
border-radius: 20px;
|
| 1787 |
+
}
|
| 1788 |
+
|
| 1789 |
+
.bte-upload-preview-image {
|
| 1790 |
+
width: calc(100% - 36px);
|
| 1791 |
+
height: calc(100% - 36px);
|
| 1792 |
+
object-fit: cover;
|
| 1793 |
+
filter: blur(1.8px) saturate(0.78) contrast(0.92);
|
| 1794 |
+
transform: scale(1.03);
|
| 1795 |
+
box-shadow: 0 16px 35px rgba(18, 32, 56, 0.08);
|
| 1796 |
+
}
|
| 1797 |
+
|
| 1798 |
+
.bte-upload-preview-placeholder {
|
| 1799 |
background:
|
| 1800 |
linear-gradient(180deg, rgba(255, 255, 255, 0.97), rgba(245, 248, 252, 0.96));
|
| 1801 |
border: 1px solid rgba(217, 226, 238, 0.82);
|
|
|
|
| 1862 |
}
|
| 1863 |
|
| 1864 |
.bte-preview-watermark {
|
| 1865 |
+
position: absolute;
|
| 1866 |
+
left: 34px;
|
| 1867 |
+
right: 34px;
|
| 1868 |
+
bottom: 28px;
|
| 1869 |
color: rgba(17, 24, 39, 0.2);
|
| 1870 |
font-size: 18px;
|
| 1871 |
font-weight: 700;
|