Simon Clematide
Add Annotate & Export tab and public API endpoints (/annotate, /render, /models)
ad9385f Download core/tree_export.py from simon-clmtd/universal-dependencies-visualizer: direct link, hf CLI and curl.
- Browser
- Download file 2.94 kB
-
https://huggingface.co/spaces/simon-clmtd/universal-dependencies-visualizer/resolve/main/core/tree_export.py
- Command line
-
hf download hf://spaces/simon-clmtd/universal-dependencies-visualizer/core/tree_export.py
-
curl -L -o tree_export.py https://huggingface.co/spaces/simon-clmtd/universal-dependencies-visualizer/resolve/main/core/tree_export.py
2.94 kB
| """ | |
| Server-side export of rendered dependency trees. | |
| The trees are drawn by conllu.js and brat in a browser. To return the same | |
| SVG/PNG files as the UI download buttons, this module loads the visualizer | |
| document in headless Chromium (Playwright) and calls its export functions. | |
| """ | |
| import base64 | |
| import subprocess | |
| import sys | |
| import threading | |
| from typing import Dict | |
| from visualizer.template import build_visualizer_document | |
| RENDER_TIMEOUT_MS = 45000 | |
| _browser_lock = threading.Lock() | |
| _browser_installed = False | |
| class TreeExportError(RuntimeError): | |
| pass | |
| def _ensure_chromium() -> None: | |
| """Installs Playwright's Chromium once per process if it is missing.""" | |
| global _browser_installed | |
| if _browser_installed: | |
| return | |
| subprocess.run( | |
| [sys.executable, "-m", "playwright", "install", "chromium"], | |
| check=True, capture_output=True, | |
| ) | |
| _browser_installed = True | |
| def export_tree_images(conllu_text: str, title: str = "", png_scale: int = 2) -> Dict[str, bytes]: | |
| """ | |
| Renders CoNLL-U in headless Chromium and returns {"svg": bytes, "png": bytes}. | |
| Calls are serialized: Playwright's sync API is not shared across threads. | |
| """ | |
| if not conllu_text or not conllu_text.strip(): | |
| raise TreeExportError("Empty CoNLL-U input.") | |
| from playwright.sync_api import sync_playwright, Error as PlaywrightError | |
| document = build_visualizer_document(conllu_text, title=title) | |
| with _browser_lock: | |
| try: | |
| _ensure_chromium() | |
| with sync_playwright() as playwright: | |
| browser = playwright.chromium.launch() | |
| try: | |
| page = browser.new_page(viewport={"width": 1600, "height": 900}) | |
| page.set_content(document, wait_until="load") | |
| page.wait_for_function( | |
| "() => ['ready', 'error'].includes(window.visualizerDiagnostics?.stage)", | |
| timeout=RENDER_TIMEOUT_MS, | |
| ) | |
| diagnostics = page.evaluate( | |
| "() => ({stage: visualizerDiagnostics.stage, error: visualizerDiagnostics.error || ''})" | |
| ) | |
| if diagnostics["stage"] != "ready": | |
| raise TreeExportError(diagnostics["error"] or "Visualization failed.") | |
| svg = page.evaluate("() => getPreparedSVG().xml") | |
| png_url = page.evaluate("scale => exportPNGDataURL(scale)", png_scale) | |
| finally: | |
| browser.close() | |
| except PlaywrightError as error: | |
| raise TreeExportError(f"Headless rendering failed: {error}") from error | |
| except subprocess.CalledProcessError as error: | |
| raise TreeExportError("Could not install the headless browser.") from error | |
| return { | |
| "svg": svg.encode("utf-8"), | |
| "png": base64.b64decode(png_url.split(",", 1)[1]), | |
| } | |