demo_gradio.py 35 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948
  1. """
  2. Layout Inference Web Application with Gradio
  3. A Gradio-based layout inference tool that supports image uploads and multiple backend inference engines.
  4. It adopts a reference-style interface design while preserving the original inference logic.
  5. """
  6. import gradio as gr
  7. import json
  8. import os
  9. import io
  10. import tempfile
  11. import base64
  12. import zipfile
  13. import uuid
  14. import re
  15. from pathlib import Path
  16. from PIL import Image
  17. import requests
  18. # Local tool imports
  19. from dots_ocr.utils import dict_promptmode_to_prompt
  20. from dots_ocr.utils.consts import MIN_PIXELS, MAX_PIXELS
  21. from dots_ocr.utils.demo_utils.display import read_image
  22. from dots_ocr.utils.doc_utils import load_images_from_pdf
  23. # Add DotsOCRParser import
  24. from dots_ocr.parser import DotsOCRParser
  25. # ==================== Configuration ====================
  26. DEFAULT_CONFIG = {
  27. 'ip': "127.0.0.1",
  28. 'port_vllm': 8000,
  29. 'min_pixels': MIN_PIXELS,
  30. 'max_pixels': MAX_PIXELS,
  31. 'test_images_dir': "./assets/showcase_origin",
  32. }
  33. # ==================== Global Variables ====================
  34. # Store current configuration
  35. current_config = DEFAULT_CONFIG.copy()
  36. # Create DotsOCRParser instance
  37. dots_parser = DotsOCRParser(
  38. ip=DEFAULT_CONFIG['ip'],
  39. port=DEFAULT_CONFIG['port_vllm'],
  40. dpi=200,
  41. min_pixels=DEFAULT_CONFIG['min_pixels'],
  42. max_pixels=DEFAULT_CONFIG['max_pixels']
  43. )
  44. # Store processing results
  45. processing_results = {
  46. 'original_image': None,
  47. 'processed_image': None,
  48. 'layout_result': None,
  49. 'markdown_content': None,
  50. 'cells_data': None,
  51. 'temp_dir': None,
  52. 'session_id': None,
  53. 'result_paths': None,
  54. 'pdf_results': None # Store multi-page PDF results
  55. }
  56. # PDF caching mechanism
  57. pdf_cache = {
  58. "images": [],
  59. "current_page": 0,
  60. "total_pages": 0,
  61. "file_type": None, # 'image' or 'pdf'
  62. "is_parsed": False, # Whether it has been parsed
  63. "results": [] # Store parsing results for each page
  64. }
  65. def read_image_v2(img):
  66. """Reads an image, supports URLs and local paths"""
  67. if isinstance(img, str) and img.startswith(("http://", "https://")):
  68. with requests.get(img, stream=True) as response:
  69. response.raise_for_status()
  70. img = Image.open(io.BytesIO(response.content))
  71. elif isinstance(img, str):
  72. img, _, _ = read_image(img, use_native=True)
  73. elif isinstance(img, Image.Image):
  74. pass
  75. else:
  76. raise ValueError(f"Invalid image type: {type(img)}")
  77. return img
  78. def load_file_for_preview(file_path):
  79. """Loads a file for preview, supports PDF and image files"""
  80. global pdf_cache
  81. if not file_path or not os.path.exists(file_path):
  82. return None, "<div id='page_info_box'>0 / 0</div>"
  83. file_ext = os.path.splitext(file_path)[1].lower()
  84. if file_ext == '.pdf':
  85. try:
  86. # Read PDF and convert to images (one image per page)
  87. pages = load_images_from_pdf(file_path)
  88. pdf_cache["file_type"] = "pdf"
  89. except Exception as e:
  90. return None, f"<div id='page_info_box'>PDF loading failed: {str(e)}</div>"
  91. elif file_ext in ['.jpg', '.jpeg', '.png']:
  92. # For image files, read directly as a single-page image
  93. try:
  94. image = Image.open(file_path)
  95. pages = [image]
  96. pdf_cache["file_type"] = "image"
  97. except Exception as e:
  98. return None, f"<div id='page_info_box'>Image loading failed: {str(e)}</div>"
  99. else:
  100. return None, "<div id='page_info_box'>Unsupported file format</div>"
  101. pdf_cache["images"] = pages
  102. pdf_cache["current_page"] = 0
  103. pdf_cache["total_pages"] = len(pages)
  104. pdf_cache["is_parsed"] = False
  105. pdf_cache["results"] = []
  106. return pages[0], f"<div id='page_info_box'>1 / {len(pages)}</div>"
  107. def turn_page(direction):
  108. """Page turning function"""
  109. global pdf_cache
  110. if not pdf_cache["images"]:
  111. return None, "<div id='page_info_box'>0 / 0</div>", "", ""
  112. if direction == "prev":
  113. pdf_cache["current_page"] = max(0, pdf_cache["current_page"] - 1)
  114. elif direction == "next":
  115. pdf_cache["current_page"] = min(pdf_cache["total_pages"] - 1, pdf_cache["current_page"] + 1)
  116. index = pdf_cache["current_page"]
  117. current_image = pdf_cache["images"][index] # Use the original image by default
  118. page_info = f"<div id='page_info_box'>{index + 1} / {pdf_cache['total_pages']}</div>"
  119. # If parsed, display the results for the current page
  120. current_md = ""
  121. current_md_raw = ""
  122. current_json = ""
  123. if pdf_cache["is_parsed"] and index < len(pdf_cache["results"]):
  124. result = pdf_cache["results"][index]
  125. if 'md_content' in result:
  126. # Get the raw markdown content
  127. current_md_raw = result['md_content']
  128. # Process the content after LaTeX rendering
  129. current_md = result['md_content'] if result['md_content'] else ""
  130. if 'cells_data' in result:
  131. try:
  132. current_json = json.dumps(result['cells_data'], ensure_ascii=False, indent=2)
  133. except:
  134. current_json = str(result.get('cells_data', ''))
  135. # Use the image with layout boxes (if available)
  136. if 'layout_image' in result and result['layout_image']:
  137. current_image = result['layout_image']
  138. return current_image, page_info, current_json
  139. def get_test_images():
  140. """Gets the list of test images"""
  141. test_images = []
  142. test_dir = current_config['test_images_dir']
  143. if os.path.exists(test_dir):
  144. test_images = [os.path.join(test_dir, name) for name in os.listdir(test_dir)
  145. if name.lower().endswith(('.png', '.jpg', '.jpeg', '.pdf'))]
  146. return test_images
  147. def convert_image_to_base64(image):
  148. """Converts a PIL image to base64 encoding"""
  149. buffered = io.BytesIO()
  150. image.save(buffered, format="PNG")
  151. img_str = base64.b64encode(buffered.getvalue()).decode()
  152. return f"data:image/png;base64,{img_str}"
  153. def create_temp_session_dir():
  154. """Creates a unique temporary directory for each processing request"""
  155. session_id = uuid.uuid4().hex[:8]
  156. temp_dir = os.path.join(tempfile.gettempdir(), f"dots_ocr_demo_{session_id}")
  157. os.makedirs(temp_dir, exist_ok=True)
  158. return temp_dir, session_id
  159. def parse_image_with_high_level_api(parser, image, prompt_mode, fitz_preprocess=False):
  160. """
  161. Processes using the high-level API parse_image from DotsOCRParser
  162. """
  163. # Create a temporary session directory
  164. temp_dir, session_id = create_temp_session_dir()
  165. try:
  166. # Save the PIL Image as a temporary file
  167. temp_image_path = os.path.join(temp_dir, f"input_{session_id}.png")
  168. image.save(temp_image_path, "PNG")
  169. # Use the high-level API parse_image
  170. filename = f"demo_{session_id}"
  171. results = parser.parse_image(
  172. # input_path=temp_image_path,
  173. input_path=image,
  174. filename=filename,
  175. prompt_mode=prompt_mode,
  176. save_dir=temp_dir,
  177. fitz_preprocess=fitz_preprocess
  178. )
  179. # Parse the results
  180. if not results:
  181. raise ValueError("No results returned from parser")
  182. result = results[0] # parse_image returns a list with a single result
  183. # Read the result files
  184. layout_image = None
  185. cells_data = None
  186. md_content = None
  187. raw_response = None
  188. filtered = False
  189. # Read the layout image
  190. if 'layout_image_path' in result and os.path.exists(result['layout_image_path']):
  191. layout_image = Image.open(result['layout_image_path'])
  192. # Read the JSON data
  193. if 'layout_info_path' in result and os.path.exists(result['layout_info_path']):
  194. with open(result['layout_info_path'], 'r', encoding='utf-8') as f:
  195. cells_data = json.load(f)
  196. # Read the Markdown content
  197. if 'md_content_path' in result and os.path.exists(result['md_content_path']):
  198. with open(result['md_content_path'], 'r', encoding='utf-8') as f:
  199. md_content = f.read()
  200. # Check for the raw response file (when JSON parsing fails)
  201. if 'filtered' in result:
  202. filtered = result['filtered']
  203. return {
  204. 'layout_image': layout_image,
  205. 'cells_data': cells_data,
  206. 'md_content': md_content,
  207. 'filtered': filtered,
  208. 'temp_dir': temp_dir,
  209. 'session_id': session_id,
  210. 'result_paths': result,
  211. 'input_width': result['input_width'],
  212. 'input_height': result['input_height'],
  213. }
  214. except Exception as e:
  215. # Clean up the temporary directory on error
  216. import shutil
  217. if os.path.exists(temp_dir):
  218. shutil.rmtree(temp_dir, ignore_errors=True)
  219. raise e
  220. def parse_pdf_with_high_level_api(parser, pdf_path, prompt_mode):
  221. """
  222. Processes using the high-level API parse_pdf from DotsOCRParser
  223. """
  224. # Create a temporary session directory
  225. temp_dir, session_id = create_temp_session_dir()
  226. try:
  227. # Use the high-level API parse_pdf
  228. filename = f"demo_{session_id}"
  229. results = parser.parse_pdf(
  230. input_path=pdf_path,
  231. filename=filename,
  232. prompt_mode=prompt_mode,
  233. save_dir=temp_dir
  234. )
  235. # Parse the results
  236. if not results:
  237. raise ValueError("No results returned from parser")
  238. # Handle multi-page results
  239. parsed_results = []
  240. all_md_content = []
  241. all_cells_data = []
  242. for i, result in enumerate(results):
  243. page_result = {
  244. 'page_no': result.get('page_no', i),
  245. 'layout_image': None,
  246. 'cells_data': None,
  247. 'md_content': None,
  248. 'filtered': False
  249. }
  250. # Read the layout image
  251. if 'layout_image_path' in result and os.path.exists(result['layout_image_path']):
  252. page_result['layout_image'] = Image.open(result['layout_image_path'])
  253. # Read the JSON data
  254. if 'layout_info_path' in result and os.path.exists(result['layout_info_path']):
  255. with open(result['layout_info_path'], 'r', encoding='utf-8') as f:
  256. page_result['cells_data'] = json.load(f)
  257. all_cells_data.extend(page_result['cells_data'])
  258. # Read the Markdown content
  259. if 'md_content_path' in result and os.path.exists(result['md_content_path']):
  260. with open(result['md_content_path'], 'r', encoding='utf-8') as f:
  261. page_content = f.read()
  262. page_result['md_content'] = page_content
  263. all_md_content.append(page_content)
  264. # Check for the raw response file (when JSON parsing fails)
  265. page_result['filtered'] = False
  266. if 'filtered' in page_result:
  267. page_result['filtered'] = page_result['filtered']
  268. parsed_results.append(page_result)
  269. # Merge the content of all pages
  270. combined_md = "\n\n---\n\n".join(all_md_content) if all_md_content else ""
  271. return {
  272. 'parsed_results': parsed_results,
  273. 'combined_md_content': combined_md,
  274. 'combined_cells_data': all_cells_data,
  275. 'temp_dir': temp_dir,
  276. 'session_id': session_id,
  277. 'total_pages': len(results)
  278. }
  279. except Exception as e:
  280. # Clean up the temporary directory on error
  281. import shutil
  282. if os.path.exists(temp_dir):
  283. shutil.rmtree(temp_dir, ignore_errors=True)
  284. raise e
  285. # ==================== Core Processing Function ====================
  286. def process_image_inference(test_image_input, file_input,
  287. prompt_mode, server_ip, server_port, min_pixels, max_pixels,
  288. fitz_preprocess=False
  289. ):
  290. """Core function to handle image/PDF inference"""
  291. global current_config, processing_results, dots_parser, pdf_cache
  292. # First, clean up previous processing results to avoid confusion with the download button
  293. if processing_results.get('temp_dir') and os.path.exists(processing_results['temp_dir']):
  294. import shutil
  295. try:
  296. shutil.rmtree(processing_results['temp_dir'], ignore_errors=True)
  297. except Exception as e:
  298. print(f"Failed to clean up previous temporary directory: {e}")
  299. # Reset processing results
  300. processing_results = {
  301. 'original_image': None,
  302. 'processed_image': None,
  303. 'layout_result': None,
  304. 'markdown_content': None,
  305. 'cells_data': None,
  306. 'temp_dir': None,
  307. 'session_id': None,
  308. 'result_paths': None,
  309. 'pdf_results': None
  310. }
  311. # Update configuration
  312. current_config.update({
  313. 'ip': server_ip,
  314. 'port_vllm': server_port,
  315. 'min_pixels': min_pixels,
  316. 'max_pixels': max_pixels
  317. })
  318. # Update parser configuration
  319. dots_parser.ip = server_ip
  320. dots_parser.port = server_port
  321. dots_parser.min_pixels = min_pixels
  322. dots_parser.max_pixels = max_pixels
  323. # Determine the input source
  324. input_file_path = None
  325. image = None
  326. # Prioritize file input (supports PDF)
  327. if file_input is not None:
  328. input_file_path = file_input
  329. file_ext = os.path.splitext(input_file_path)[1].lower()
  330. if file_ext == '.pdf':
  331. # PDF file processing
  332. try:
  333. return process_pdf_file(input_file_path, prompt_mode)
  334. except Exception as e:
  335. return None, f"PDF processing failed: {e}", "", "", gr.update(value=None), None, ""
  336. elif file_ext in ['.jpg', '.jpeg', '.png']:
  337. # Image file processing
  338. try:
  339. image = Image.open(input_file_path)
  340. except Exception as e:
  341. return None, f"Failed to read image file: {e}", "", "", gr.update(value=None), None, ""
  342. # If no file input, check the test image input
  343. if image is None:
  344. if test_image_input and test_image_input != "":
  345. file_ext = os.path.splitext(test_image_input)[1].lower()
  346. if file_ext == '.pdf':
  347. return process_pdf_file(test_image_input, prompt_mode)
  348. else:
  349. try:
  350. image = read_image_v2(test_image_input)
  351. except Exception as e:
  352. return None, f"Failed to read test image: {e}", "", "", gr.update(value=None), gr.update(value=None), None, ""
  353. if image is None:
  354. return None, "Please upload image/PDF file or select test image", "", "", gr.update(value=None), None, ""
  355. try:
  356. # Clear PDF cache (for image processing)
  357. pdf_cache["images"] = []
  358. pdf_cache["current_page"] = 0
  359. pdf_cache["total_pages"] = 0
  360. pdf_cache["is_parsed"] = False
  361. pdf_cache["results"] = []
  362. # Process using the high-level API of DotsOCRParser
  363. original_image = image
  364. parse_result = parse_image_with_high_level_api(dots_parser, image, prompt_mode, fitz_preprocess)
  365. # Extract parsing results
  366. layout_image = parse_result['layout_image']
  367. cells_data = parse_result['cells_data']
  368. md_content = parse_result['md_content']
  369. filtered = parse_result['filtered']
  370. # Handle parsing failure case
  371. if filtered:
  372. # JSON parsing failed, only text content is available
  373. info_text = f"""
  374. **Image Information:**
  375. - Original Size: {original_image.width} x {original_image.height}
  376. - Processing: JSON parsing failed, using cleaned text output
  377. - Server: {current_config['ip']}:{current_config['port_vllm']}
  378. - Session ID: {parse_result['session_id']}
  379. """
  380. # Store results
  381. processing_results.update({
  382. 'original_image': original_image,
  383. 'processed_image': None,
  384. 'layout_result': None,
  385. 'markdown_content': md_content,
  386. 'cells_data': None,
  387. 'temp_dir': parse_result['temp_dir'],
  388. 'session_id': parse_result['session_id'],
  389. 'result_paths': parse_result['result_paths']
  390. })
  391. return (
  392. original_image, # No layout image
  393. info_text,
  394. md_content,
  395. md_content, # Display raw markdown text
  396. gr.update(visible=False), # Hide download button
  397. None, # Page info
  398. "" # Current page JSON output
  399. )
  400. # JSON parsing successful case
  401. # Save the raw markdown content (before LaTeX processing)
  402. md_content_raw = md_content or "No markdown content generated"
  403. # Store results
  404. processing_results.update({
  405. 'original_image': original_image,
  406. 'processed_image': None, # High-level API does not return processed_image
  407. 'layout_result': layout_image,
  408. 'markdown_content': md_content,
  409. 'cells_data': cells_data,
  410. 'temp_dir': parse_result['temp_dir'],
  411. 'session_id': parse_result['session_id'],
  412. 'result_paths': parse_result['result_paths']
  413. })
  414. # Prepare display information
  415. num_elements = len(cells_data) if cells_data else 0
  416. info_text = f"""
  417. **Image Information:**
  418. - Original Size: {original_image.width} x {original_image.height}
  419. - Model Input Size: {parse_result['input_width']} x {parse_result['input_height']}
  420. - Server: {current_config['ip']}:{current_config['port_vllm']}
  421. - Detected {num_elements} layout elements
  422. - Session ID: {parse_result['session_id']}
  423. """
  424. # Current page JSON output
  425. current_json = ""
  426. if cells_data:
  427. try:
  428. current_json = json.dumps(cells_data, ensure_ascii=False, indent=2)
  429. except:
  430. current_json = str(cells_data)
  431. # Create the download ZIP file
  432. download_zip_path = None
  433. if parse_result['temp_dir']:
  434. download_zip_path = os.path.join(parse_result['temp_dir'], f"layout_results_{parse_result['session_id']}.zip")
  435. try:
  436. with zipfile.ZipFile(download_zip_path, 'w', zipfile.ZIP_DEFLATED) as zipf:
  437. for root, dirs, files in os.walk(parse_result['temp_dir']):
  438. for file in files:
  439. if file.endswith('.zip'):
  440. continue
  441. file_path = os.path.join(root, file)
  442. arcname = os.path.relpath(file_path, parse_result['temp_dir'])
  443. zipf.write(file_path, arcname)
  444. except Exception as e:
  445. print(f"Failed to create download ZIP: {e}")
  446. download_zip_path = None
  447. return (
  448. layout_image,
  449. info_text,
  450. md_content or "No markdown content generated",
  451. md_content_raw, # Raw markdown text
  452. gr.update(value=download_zip_path, visible=True) if download_zip_path else gr.update(visible=False), # Set the download file
  453. None, # Page info (not displayed for image processing)
  454. current_json # Current page JSON
  455. )
  456. except Exception as e:
  457. return None, f"Error during processing: {e}", "", "", gr.update(value=None), None, ""
  458. def process_pdf_file(pdf_path, prompt_mode):
  459. """Dedicated function for processing PDF files"""
  460. global pdf_cache, processing_results, dots_parser
  461. try:
  462. # First, load the PDF for preview
  463. preview_image, page_info = load_file_for_preview(pdf_path)
  464. # Parse the PDF using DotsOCRParser
  465. pdf_result = parse_pdf_with_high_level_api(dots_parser, pdf_path, prompt_mode)
  466. # Update the PDF cache
  467. pdf_cache["is_parsed"] = True
  468. pdf_cache["results"] = pdf_result['parsed_results']
  469. # Handle LaTeX table rendering
  470. combined_md = pdf_result['combined_md_content']
  471. combined_md_raw = combined_md or "No markdown content generated" # Save the raw content
  472. # Store results
  473. processing_results.update({
  474. 'original_image': None,
  475. 'processed_image': None,
  476. 'layout_result': None,
  477. 'markdown_content': combined_md,
  478. 'cells_data': pdf_result['combined_cells_data'],
  479. 'temp_dir': pdf_result['temp_dir'],
  480. 'session_id': pdf_result['session_id'],
  481. 'result_paths': None,
  482. 'pdf_results': pdf_result['parsed_results']
  483. })
  484. # Prepare display information
  485. total_elements = len(pdf_result['combined_cells_data'])
  486. info_text = f"""
  487. **PDF Information:**
  488. - Total Pages: {pdf_result['total_pages']}
  489. - Server: {current_config['ip']}:{current_config['port_vllm']}
  490. - Total Detected Elements: {total_elements}
  491. - Session ID: {pdf_result['session_id']}
  492. """
  493. # Content of the current page (first page)
  494. current_page_md = ""
  495. current_page_md_raw = ""
  496. current_page_json = ""
  497. current_page_layout_image = preview_image # Use the original preview image by default
  498. if pdf_cache["results"] and len(pdf_cache["results"]) > 0:
  499. current_result = pdf_cache["results"][0]
  500. if current_result['md_content']:
  501. # Raw markdown content
  502. current_page_md_raw = current_result['md_content']
  503. # Process the content after LaTeX rendering
  504. current_page_md = current_result['md_content']
  505. if current_result['cells_data']:
  506. try:
  507. current_page_json = json.dumps(current_result['cells_data'], ensure_ascii=False, indent=2)
  508. except:
  509. current_page_json = str(current_result['cells_data'])
  510. # Use the image with layout boxes (if available)
  511. if 'layout_image' in current_result and current_result['layout_image']:
  512. current_page_layout_image = current_result['layout_image']
  513. # Create the download ZIP file
  514. download_zip_path = None
  515. if pdf_result['temp_dir']:
  516. download_zip_path = os.path.join(pdf_result['temp_dir'], f"layout_results_{pdf_result['session_id']}.zip")
  517. try:
  518. with zipfile.ZipFile(download_zip_path, 'w', zipfile.ZIP_DEFLATED) as zipf:
  519. for root, dirs, files in os.walk(pdf_result['temp_dir']):
  520. for file in files:
  521. if file.endswith('.zip'):
  522. continue
  523. file_path = os.path.join(root, file)
  524. arcname = os.path.relpath(file_path, pdf_result['temp_dir'])
  525. zipf.write(file_path, arcname)
  526. except Exception as e:
  527. print(f"Failed to create download ZIP: {e}")
  528. download_zip_path = None
  529. return (
  530. current_page_layout_image, # Use the image with layout boxes
  531. info_text,
  532. combined_md or "No markdown content generated", # Display the markdown for the entire PDF
  533. combined_md_raw or "No markdown content generated", # Display the raw markdown for the entire PDF
  534. gr.update(value=download_zip_path, visible=True) if download_zip_path else gr.update(visible=False), # Set the download file
  535. page_info,
  536. current_page_json
  537. )
  538. except Exception as e:
  539. # Reset the PDF cache
  540. pdf_cache["images"] = []
  541. pdf_cache["current_page"] = 0
  542. pdf_cache["total_pages"] = 0
  543. pdf_cache["is_parsed"] = False
  544. pdf_cache["results"] = []
  545. raise e
  546. def clear_all_data():
  547. """Clears all data"""
  548. global processing_results, pdf_cache
  549. # Clean up the temporary directory
  550. if processing_results.get('temp_dir') and os.path.exists(processing_results['temp_dir']):
  551. import shutil
  552. try:
  553. shutil.rmtree(processing_results['temp_dir'], ignore_errors=True)
  554. except Exception as e:
  555. print(f"Failed to clean up temporary directory: {e}")
  556. # Reset processing results
  557. processing_results = {
  558. 'original_image': None,
  559. 'processed_image': None,
  560. 'layout_result': None,
  561. 'markdown_content': None,
  562. 'cells_data': None,
  563. 'temp_dir': None,
  564. 'session_id': None,
  565. 'result_paths': None,
  566. 'pdf_results': None
  567. }
  568. # Reset the PDF cache
  569. pdf_cache = {
  570. "images": [],
  571. "current_page": 0,
  572. "total_pages": 0,
  573. "file_type": None,
  574. "is_parsed": False,
  575. "results": []
  576. }
  577. return (
  578. None, # Clear file input
  579. "", # Clear test image selection
  580. None, # Clear result image
  581. "Waiting for processing results...", # Reset info display
  582. "## Waiting for processing results...", # Reset Markdown display
  583. "🕐 Waiting for parsing result...", # Clear raw Markdown text
  584. gr.update(visible=False), # Hide download button
  585. "<div id='page_info_box'>0 / 0</div>", # Reset page info
  586. "🕐 Waiting for parsing result..." # Clear current page JSON
  587. )
  588. def update_prompt_display(prompt_mode):
  589. """Updates the prompt display content"""
  590. return dict_promptmode_to_prompt[prompt_mode]
  591. # ==================== Gradio Interface ====================
  592. def create_gradio_interface():
  593. """Creates the Gradio interface"""
  594. # CSS styles, matching the reference style
  595. css = """
  596. #parse_button {
  597. background: #FF576D !important; /* !important 确保覆盖主题默认样式 */
  598. border-color: #FF576D !important;
  599. }
  600. /* 鼠标悬停时的颜色 */
  601. #parse_button:hover {
  602. background: #F72C49 !important;
  603. border-color: #F72C49 !important;
  604. }
  605. #page_info_html {
  606. display: flex;
  607. align-items: center;
  608. justify-content: center;
  609. height: 100%;
  610. margin: 0 12px;
  611. }
  612. #page_info_box {
  613. padding: 8px 20px;
  614. font-size: 16px;
  615. border: 1px solid #bbb;
  616. border-radius: 8px;
  617. background-color: #f8f8f8;
  618. text-align: center;
  619. min-width: 80px;
  620. box-shadow: 0 1px 3px rgba(0,0,0,0.1);
  621. }
  622. #markdown_output {
  623. min-height: 800px;
  624. overflow: auto;
  625. }
  626. footer {
  627. visibility: hidden;
  628. }
  629. #info_box {
  630. padding: 10px;
  631. background-color: #f8f9fa;
  632. border-radius: 8px;
  633. border: 1px solid #dee2e6;
  634. margin: 10px 0;
  635. font-size: 14px;
  636. }
  637. #result_image {
  638. border-radius: 8px;
  639. }
  640. #markdown_tabs {
  641. height: 100%;
  642. }
  643. """
  644. with gr.Blocks(theme="ocean", css=css, title='dots.ocr') as demo:
  645. # Title
  646. gr.HTML("""
  647. <div style="display: flex; align-items: center; justify-content: center; margin-bottom: 20px;">
  648. <h1 style="margin: 0; font-size: 2em;">🔍 dots.ocr</h1>
  649. </div>
  650. <div style="text-align: center; margin-bottom: 10px;">
  651. <em>Supports image/PDF layout analysis and structured output</em>
  652. </div>
  653. """)
  654. with gr.Row():
  655. # Left side: Input and Configuration
  656. with gr.Column(scale=1, elem_id="left-panel"):
  657. gr.Markdown("### 📥 Upload & Select")
  658. file_input = gr.File(
  659. label="Upload PDF/Image",
  660. type="filepath",
  661. file_types=[".pdf", ".jpg", ".jpeg", ".png"],
  662. )
  663. test_images = get_test_images()
  664. test_image_input = gr.Dropdown(
  665. label="Or Select an Example",
  666. choices=[""] + test_images,
  667. value="",
  668. )
  669. gr.Markdown("### ⚙️ Prompt & Actions")
  670. prompt_mode = gr.Dropdown(
  671. label="Select Prompt",
  672. choices=["prompt_layout_all_en", "prompt_layout_only_en", "prompt_ocr"],
  673. value="prompt_layout_all_en",
  674. show_label=True
  675. )
  676. # Display current prompt content
  677. prompt_display = gr.Textbox(
  678. label="Current Prompt Content",
  679. value=dict_promptmode_to_prompt[list(dict_promptmode_to_prompt.keys())[0]],
  680. lines=4,
  681. max_lines=8,
  682. interactive=False,
  683. show_copy_button=True
  684. )
  685. with gr.Row():
  686. process_btn = gr.Button("🔍 Parse", variant="primary", scale=2, elem_id="parse_button")
  687. clear_btn = gr.Button("🗑️ Clear", variant="secondary", scale=1)
  688. with gr.Accordion("🛠️ Advanced Configuration", open=False):
  689. fitz_preprocess = gr.Checkbox(
  690. label="Enable fitz_preprocess for images",
  691. value=True,
  692. info="Processes image via a PDF-like pipeline (image->pdf->200dpi image). Recommended if your image DPI is low."
  693. )
  694. with gr.Row():
  695. server_ip = gr.Textbox(label="Server IP", value=DEFAULT_CONFIG['ip'])
  696. server_port = gr.Number(label="Port", value=DEFAULT_CONFIG['port_vllm'], precision=0)
  697. with gr.Row():
  698. min_pixels = gr.Number(label="Min Pixels", value=DEFAULT_CONFIG['min_pixels'], precision=0)
  699. max_pixels = gr.Number(label="Max Pixels", value=DEFAULT_CONFIG['max_pixels'], precision=0)
  700. # Right side: Result Display
  701. with gr.Column(scale=6, variant="compact"):
  702. with gr.Row():
  703. # Result Image
  704. with gr.Column(scale=3):
  705. gr.Markdown("### 👁️ File Preview")
  706. result_image = gr.Image(
  707. label="Layout Preview",
  708. visible=True,
  709. height=800,
  710. show_label=False
  711. )
  712. # Page navigation (shown during PDF preview)
  713. with gr.Row():
  714. prev_btn = gr.Button("⬅ Previous", size="sm")
  715. page_info = gr.HTML(
  716. value="<div id='page_info_box'>0 / 0</div>",
  717. elem_id="page_info_html"
  718. )
  719. next_btn = gr.Button("Next ➡", size="sm")
  720. # Info Display
  721. info_display = gr.Markdown(
  722. "Waiting for processing results...",
  723. elem_id="info_box"
  724. )
  725. # Markdown Result
  726. with gr.Column(scale=3):
  727. gr.Markdown("### ✔️ Result Display")
  728. with gr.Tabs(elem_id="markdown_tabs"):
  729. with gr.TabItem("Markdown Render Preview"):
  730. md_output = gr.Markdown(
  731. "## Please click the parse button to parse or select for single-task recognition...",
  732. label="Markdown Preview",
  733. max_height=600,
  734. latex_delimiters=[
  735. {"left": "$$", "right": "$$", "display": True},
  736. {"left": "$", "right": "$", "display": False},
  737. ],
  738. show_copy_button=False,
  739. elem_id="markdown_output"
  740. )
  741. with gr.TabItem("Markdown Raw Text"):
  742. md_raw_output = gr.Textbox(
  743. value="🕐 Waiting for parsing result...",
  744. label="Markdown Raw Text",
  745. max_lines=100,
  746. lines=38,
  747. show_copy_button=True,
  748. elem_id="markdown_output",
  749. show_label=False
  750. )
  751. with gr.TabItem("Current Page JSON"):
  752. current_page_json = gr.Textbox(
  753. value="🕐 Waiting for parsing result...",
  754. label="Current Page JSON",
  755. max_lines=100,
  756. lines=38,
  757. show_copy_button=True,
  758. elem_id="markdown_output",
  759. show_label=False
  760. )
  761. # Download Button
  762. with gr.Row():
  763. download_btn = gr.DownloadButton(
  764. "⬇️ Download Results",
  765. visible=False
  766. )
  767. # When the prompt mode changes, update the display content
  768. prompt_mode.change(
  769. fn=update_prompt_display,
  770. inputs=prompt_mode,
  771. outputs=prompt_display,
  772. show_progress=False
  773. )
  774. # Show preview on file upload
  775. file_input.upload(
  776. fn=load_file_for_preview,
  777. inputs=file_input,
  778. outputs=[result_image, page_info],
  779. show_progress=False
  780. )
  781. # Page navigation
  782. prev_btn.click(
  783. fn=lambda: turn_page("prev"),
  784. outputs=[result_image, page_info, current_page_json],
  785. show_progress=False
  786. )
  787. next_btn.click(
  788. fn=lambda: turn_page("next"),
  789. outputs=[result_image, page_info, current_page_json],
  790. show_progress=False
  791. )
  792. process_btn.click(
  793. fn=process_image_inference,
  794. inputs=[
  795. test_image_input, file_input,
  796. prompt_mode, server_ip, server_port, min_pixels, max_pixels,
  797. fitz_preprocess
  798. ],
  799. outputs=[
  800. result_image, info_display, md_output, md_raw_output,
  801. download_btn, page_info, current_page_json
  802. ],
  803. show_progress=True
  804. )
  805. clear_btn.click(
  806. fn=clear_all_data,
  807. outputs=[
  808. file_input, test_image_input,
  809. result_image, info_display, md_output, md_raw_output,
  810. download_btn, page_info, current_page_json
  811. ],
  812. show_progress=False
  813. )
  814. return demo
  815. # ==================== Main Program ====================
  816. if __name__ == "__main__":
  817. demo = create_gradio_interface()
  818. demo.queue().launch(
  819. server_name="0.0.0.0",
  820. server_port=7860,
  821. debug=True
  822. )