muk42 commited on
Commit
c75bfbf
·
verified ·
1 Parent(s): 625e2d6

Upload 3 files

Browse files
Files changed (2) hide show
  1. Dockerfile.dockerfile +24 -0
  2. app.py +198 -96
Dockerfile.dockerfile ADDED
@@ -0,0 +1,24 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ FROM python:3.9-slim
2
+
3
+ # Install system dependencies
4
+ RUN apt-get update && apt-get install -y \
5
+ poppler-utils \
6
+ libgl1-mesa-glx \
7
+ fonts-dejavu \
8
+ fonts-liberation \
9
+ && rm -rf /var/lib/apt/lists/*
10
+
11
+ WORKDIR /app
12
+
13
+ # Copy requirements first for better caching
14
+ COPY requirements.txt .
15
+ RUN pip install --no-cache-dir -r requirements.txt
16
+
17
+ # Copy app code
18
+ COPY app.py .
19
+
20
+ # Expose port
21
+ EXPOSE 7860
22
+
23
+ # Run the app
24
+ CMD ["python", "app.py"]
app.py CHANGED
@@ -4,6 +4,8 @@ from pdf2image import convert_from_path
4
  from PIL import Image, ImageDraw, ImageFont
5
  import json
6
  import tempfile
 
 
7
 
8
  # Colors for annotations
9
  PARA_COLOR = (255, 0, 0) # Red for paragraph
@@ -15,6 +17,64 @@ paragraph_data = {}
15
  current_display_data = {}
16
  temp_files = []
17
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
18
  def cleanup_temp_files():
19
  """Clean up temporary files"""
20
  global temp_files
@@ -38,7 +98,24 @@ def draw_paragraphs_and_dates(page_img, paragraphs, hover_para_id=None, font_sca
38
  """Draw annotations with optional hover highlighting"""
39
  draw = ImageDraw.Draw(page_img)
40
  try:
41
- font = ImageFont.truetype("arial.ttf", 12 * font_scale)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
42
  except:
43
  font = ImageFont.load_default()
44
 
@@ -67,10 +144,15 @@ def annotate_pdf_paragraphs(pdf_path, json_path, img_dpi=150):
67
  """Load PDF and JSON, prepare data for display"""
68
  global paragraph_data
69
 
 
 
 
 
70
  with open(json_path, "r") as f:
71
  data = json.load(f)
 
72
 
73
- pages = convert_from_path(pdf_path, dpi=img_dpi)
74
  pdf_name = os.path.splitext(os.path.basename(pdf_path))[0]
75
 
76
  paragraph_data = {}
@@ -110,6 +192,7 @@ def annotate_pdf_paragraphs(pdf_path, json_path, img_dpi=150):
110
  annotated_img = draw_paragraphs_and_dates(page_img.copy(), paragraphs)
111
  annotated_pages.append(annotated_img)
112
 
 
113
  return annotated_pages, data, len(pages)
114
 
115
  def get_page_text_html(page_num, highlight_id=None):
@@ -130,14 +213,14 @@ def get_page_text_html(page_num, highlight_id=None):
130
  text = para['text']
131
  dates = para['dates']
132
 
133
- # Simple hover style
134
  if str(para_id) == str(highlight_id):
135
- style = "background-color: #fff3cd; padding: 10px; margin: 5px; border-left: 4px solid #ffc107;"
136
  else:
137
- style = "background-color: #f8f9fa; padding: 10px; margin: 5px; border-left: 2px solid #ddd;"
138
 
139
  html_parts.append(f"""
140
- <div style="{style}" class="para-{para_id}" data-page="{page_num}" data-para="{para_id}">
141
  <div style="font-weight: bold; color: #d9534f; margin-bottom: 5px;">
142
  Paragraph {para_id}
143
  </div>
@@ -174,6 +257,19 @@ def get_page_text_html(page_num, highlight_id=None):
174
 
175
  html_parts.append("</div>")
176
 
 
 
 
 
 
 
 
 
 
 
 
 
 
177
  return "".join(html_parts)
178
 
179
  def update_image_with_hover(page_num, hover_para_id):
@@ -204,12 +300,12 @@ def update_image_with_hover(page_num, hover_para_id):
204
  temp_path = create_temp_image(highlighted_img)
205
  return temp_path
206
 
207
- # Gradio App - Simplified version
208
- with gr.Blocks(title="PDF Annotation Viewer") as app:
209
- gr.Markdown("## PDF Annotation Viewer")
210
- gr.Markdown("Upload a PDF and JSON file to view annotations.")
211
 
212
- # Store current state
213
  state_pages = gr.State()
214
  state_data = gr.State()
215
  state_total_pages = gr.State(1)
@@ -217,56 +313,53 @@ with gr.Blocks(title="PDF Annotation Viewer") as app:
217
  state_current_hover = gr.State(None)
218
 
219
  with gr.Row():
220
- with gr.Column():
221
- pdf_file = gr.File(label="PDF Journal", type="filepath")
222
- json_file = gr.File(label="JSON Predictions", type="filepath")
223
- apply_btn = gr.Button("Load & Annotate", variant="primary")
224
 
225
  with gr.Row():
226
- # Left side: Image display
227
  with gr.Column(scale=2):
228
  with gr.Row():
229
- prev_btn = gr.Button("◀ Previous")
230
- page_counter = gr.Number(
231
- label="Page",
232
- value=1,
233
- minimum=1,
234
- interactive=True,
235
- precision=0
236
- )
237
- next_btn = gr.Button("Next ▶")
238
- total_pages = gr.Textbox("1", label="of", interactive=False)
239
 
240
- page_display = gr.Image(
241
  label="Annotated Page",
242
  type="filepath",
243
  height=600
244
  )
245
 
246
- # Right side: Text display
247
  with gr.Column(scale=1):
248
  text_display = gr.HTML(
249
- label="Paragraph Text with Dates",
250
  value="<div style='padding: 20px; text-align: center; color: #666;'>Upload files to begin</div>"
251
  )
252
 
253
- # Hidden inputs for hover
254
- hover_page = gr.Number(visible=False, value=1)
255
- hover_para = gr.Textbox(visible=False)
256
 
257
  # Main load function
258
- def load_pdf_and_json(pdf_file_obj, json_file_obj):
259
  """Load and process files"""
260
  cleanup_temp_files()
261
 
262
  if not pdf_file_obj or not json_file_obj:
263
- return [None, None, 1, 1, "1", None,
264
- "<div>Please upload both files</div>", None]
265
-
266
- pdf_path = pdf_file_obj.name
267
- json_path = json_file_obj.name
268
 
269
  try:
 
 
 
 
 
 
270
  # Annotate PDF
271
  pages, data, total_pages = annotate_pdf_paragraphs(pdf_path, json_path)
272
 
@@ -285,14 +378,18 @@ with gr.Blocks(title="PDF Annotation Viewer") as app:
285
  # Get text for first page
286
  text_html = get_page_text_html(1)
287
 
288
- return [pages, data, total_pages, 1,
289
- str(total_pages), first_page_path, text_html, None]
 
 
 
290
  except Exception as e:
291
- return [None, None, 1, 1, "1", None,
292
- f"<div style='color: red;'>Error: {str(e)}</div>", None]
 
293
 
294
  # Page navigation
295
- def navigate_page(direction, current_page, total_pages):
296
  current_page = int(current_page)
297
  total_pages = int(total_pages)
298
 
@@ -304,98 +401,103 @@ with gr.Blocks(title="PDF Annotation Viewer") as app:
304
  new_page = current_page
305
 
306
  # Get text for new page
307
- text_html = get_page_text_html(new_page)
308
 
309
  # Update image
310
  if current_display_data and 'pages' in current_display_data:
311
  pages = current_display_data['pages']
312
  if 0 < new_page <= len(pages):
313
- temp_path = create_temp_image(pages[new_page - 1])
314
- return [temp_path, text_html, new_page, new_page]
 
 
 
315
 
316
- return [None, text_html, new_page, new_page]
317
 
318
  # Page change
319
- def change_page(page_num):
320
  page_num = int(page_num)
321
- text_html = get_page_text_html(page_num)
322
 
323
  if current_display_data and 'pages' in current_display_data:
324
  pages = current_display_data['pages']
325
  if 0 < page_num <= len(pages):
326
- temp_path = create_temp_image(pages[page_num - 1])
327
- return [temp_path, text_html, page_num]
 
 
 
328
 
329
- return [None, text_html, page_num]
330
-
331
- # Simple hover using Gradio events
332
- def handle_hover(evt: gr.SelectData):
333
- """Handle click on text to simulate hover"""
334
- if evt.value:
335
- # Parse the paragraph ID from the clicked element
336
- # This is a simple approach - in production you'd want to use data attributes
337
- import re
338
- match = re.search(r'para-(\d+)', evt.value)
339
- if match:
340
- para_id = match.group(1)
341
- current_page = int(page_counter.value) if page_counter.value else 1
342
-
343
- # Update image with highlight
344
- if current_display_data:
345
- temp_path = update_image_with_hover(current_page, para_id)
346
- if temp_path:
347
- # Update text with highlight
348
- text_html = get_page_text_html(current_page, para_id)
349
- return [temp_path, text_html, para_id]
350
 
351
- return [page_display.value, text_display.value, None]
 
 
 
 
 
 
352
 
353
  # Connect events
354
- apply_btn.click(
355
- load_pdf_and_json,
356
  inputs=[pdf_file, json_file],
357
  outputs=[
358
  state_pages,
359
  state_data,
360
  state_total_pages,
361
- state_current_page,
362
- total_pages,
363
- page_display,
364
  text_display,
365
- state_current_hover
 
366
  ]
367
  )
368
 
369
  next_btn.click(
370
  navigate_page,
371
- inputs=[gr.Text("next", visible=False), page_counter, state_total_pages],
372
- outputs=[page_display, text_display, state_current_page, page_counter]
373
  )
374
 
375
  prev_btn.click(
376
  navigate_page,
377
- inputs=[gr.Text("prev", visible=False), page_counter, state_total_pages],
378
- outputs=[page_display, text_display, state_current_page, page_counter]
379
  )
380
 
381
- page_counter.change(
382
  change_page,
383
- inputs=[page_counter],
384
- outputs=[page_display, text_display, state_current_page]
385
  )
386
 
387
- # Simple click-to-highlight instead of hover
388
  text_display.select(
389
- handle_hover,
390
  None,
391
- [page_display, text_display, hover_para]
 
 
 
 
392
  )
393
 
394
- # Add this at the end of the file
 
 
 
395
  if __name__ == "__main__":
396
- # Clean up on exit
397
- import atexit
398
- atexit.register(cleanup_temp_files)
399
-
400
- # Launch for HuggingFace Spaces
401
- app.launch(debug=False)
 
4
  from PIL import Image, ImageDraw, ImageFont
5
  import json
6
  import tempfile
7
+ import subprocess
8
+ import sys
9
 
10
  # Colors for annotations
11
  PARA_COLOR = (255, 0, 0) # Red for paragraph
 
17
  current_display_data = {}
18
  temp_files = []
19
 
20
+ def get_poppler_path():
21
+ """Find poppler path in common locations"""
22
+ possible_paths = [
23
+ '/usr/bin',
24
+ '/usr/local/bin',
25
+ '/bin',
26
+ '/opt/homebrew/bin', # macOS
27
+ '/usr/lib/x86_64-linux-gnu', # Ubuntu/Debian
28
+ ]
29
+
30
+ for path in possible_paths:
31
+ pdftoppm_path = os.path.join(path, 'pdftoppm')
32
+ if os.path.exists(pdftoppm_path):
33
+ return path
34
+
35
+ # Try to find it using which
36
+ try:
37
+ result = subprocess.run(['which', 'pdftoppm'],
38
+ capture_output=True, text=True)
39
+ if result.returncode == 0:
40
+ return os.path.dirname(result.stdout.strip())
41
+ except:
42
+ pass
43
+
44
+ return None
45
+
46
+ def convert_pdf_to_images(pdf_path, dpi=150):
47
+ """Convert PDF to images with explicit poppler path"""
48
+ poppler_path = get_poppler_path()
49
+
50
+ print(f"DEBUG: Looking for poppler...")
51
+ print(f"DEBUG: PDF path: {pdf_path}")
52
+ print(f"DEBUG: Poppler path found: {poppler_path}")
53
+
54
+ # Check if poppler tools exist
55
+ if poppler_path:
56
+ pdftoppm_path = os.path.join(poppler_path, 'pdftoppm')
57
+ print(f"DEBUG: pdftoppm path: {pdftoppm_path}")
58
+ print(f"DEBUG: Exists: {os.path.exists(pdftoppm_path)}")
59
+
60
+ try:
61
+ if poppler_path and os.path.exists(os.path.join(poppler_path, 'pdftoppm')):
62
+ print(f"DEBUG: Using poppler path: {poppler_path}")
63
+ images = convert_from_path(
64
+ pdf_path,
65
+ dpi=dpi,
66
+ poppler_path=poppler_path
67
+ )
68
+ else:
69
+ print(f"DEBUG: Trying without explicit poppler path")
70
+ images = convert_from_path(pdf_path, dpi=dpi)
71
+
72
+ print(f"DEBUG: Successfully converted PDF, got {len(images)} pages")
73
+ return images
74
+ except Exception as e:
75
+ print(f"DEBUG: PDF conversion error: {str(e)}")
76
+ raise Exception(f"PDF conversion failed: {str(e)}")
77
+
78
  def cleanup_temp_files():
79
  """Clean up temporary files"""
80
  global temp_files
 
98
  """Draw annotations with optional hover highlighting"""
99
  draw = ImageDraw.Draw(page_img)
100
  try:
101
+ # Try different font paths
102
+ font_paths = [
103
+ "/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf",
104
+ "/usr/share/fonts/truetype/liberation/LiberationSans-Regular.ttf",
105
+ "/usr/share/fonts/truetype/freefont/FreeSans.ttf",
106
+ ]
107
+
108
+ font = None
109
+ for font_path in font_paths:
110
+ if os.path.exists(font_path):
111
+ try:
112
+ font = ImageFont.truetype(font_path, 12 * font_scale)
113
+ break
114
+ except:
115
+ continue
116
+
117
+ if font is None:
118
+ font = ImageFont.load_default()
119
  except:
120
  font = ImageFont.load_default()
121
 
 
144
  """Load PDF and JSON, prepare data for display"""
145
  global paragraph_data
146
 
147
+ print(f"DEBUG: Starting PDF annotation")
148
+ print(f"DEBUG: PDF: {pdf_path}")
149
+ print(f"DEBUG: JSON: {json_path}")
150
+
151
  with open(json_path, "r") as f:
152
  data = json.load(f)
153
+ print(f"DEBUG: JSON loaded successfully, keys: {list(data.keys())[:3]}")
154
 
155
+ pages = convert_pdf_to_images(pdf_path, dpi=img_dpi)
156
  pdf_name = os.path.splitext(os.path.basename(pdf_path))[0]
157
 
158
  paragraph_data = {}
 
192
  annotated_img = draw_paragraphs_and_dates(page_img.copy(), paragraphs)
193
  annotated_pages.append(annotated_img)
194
 
195
+ print(f"DEBUG: Annotation complete, {len(annotated_pages)} pages processed")
196
  return annotated_pages, data, len(pages)
197
 
198
  def get_page_text_html(page_num, highlight_id=None):
 
213
  text = para['text']
214
  dates = para['dates']
215
 
216
+ # Simple highlight style
217
  if str(para_id) == str(highlight_id):
218
+ style = "background-color: #fff3cd; padding: 10px; margin: 5px; border-left: 4px solid #ffc107; cursor: pointer;"
219
  else:
220
+ style = "background-color: #f8f9fa; padding: 10px; margin: 5px; border-left: 2px solid #ddd; cursor: pointer;"
221
 
222
  html_parts.append(f"""
223
+ <div style="{style}" onclick="highlightParagraph({page_num}, {para_id})">
224
  <div style="font-weight: bold; color: #d9534f; margin-bottom: 5px;">
225
  Paragraph {para_id}
226
  </div>
 
257
 
258
  html_parts.append("</div>")
259
 
260
+ # Add JavaScript for highlighting
261
+ html_parts.append("""
262
+ <script>
263
+ function highlightParagraph(pageNum, paraId) {
264
+ // Create a custom event to trigger Python function
265
+ const event = new CustomEvent('highlightPara', {
266
+ detail: { page: pageNum, para: paraId }
267
+ });
268
+ window.dispatchEvent(event);
269
+ }
270
+ </script>
271
+ """)
272
+
273
  return "".join(html_parts)
274
 
275
  def update_image_with_hover(page_num, hover_para_id):
 
300
  temp_path = create_temp_image(highlighted_img)
301
  return temp_path
302
 
303
+ # Create the Gradio app
304
+ with gr.Blocks(title="PDF Annotation Viewer", theme=gr.themes.Soft()) as app:
305
+ gr.Markdown("# PDF Annotation Viewer")
306
+ gr.Markdown("Upload a PDF file and its corresponding JSON annotations to view highlighted paragraphs and dates.")
307
 
308
+ # State variables
309
  state_pages = gr.State()
310
  state_data = gr.State()
311
  state_total_pages = gr.State(1)
 
313
  state_current_hover = gr.State(None)
314
 
315
  with gr.Row():
316
+ with gr.Column(scale=1):
317
+ pdf_file = gr.File(label="PDF File", file_types=[".pdf"])
318
+ json_file = gr.File(label="JSON File", file_types=[".json"])
319
+ load_btn = gr.Button("Load Files", variant="primary")
320
 
321
  with gr.Row():
322
+ # Image display
323
  with gr.Column(scale=2):
324
  with gr.Row():
325
+ prev_btn = gr.Button("← Previous", size="sm")
326
+ page_num = gr.Number(value=1, label="Page", minimum=1, interactive=True)
327
+ next_btn = gr.Button("Next →", size="sm")
328
+ total_pages_disp = gr.Textbox("1", label="of", interactive=False)
 
 
 
 
 
 
329
 
330
+ image_display = gr.Image(
331
  label="Annotated Page",
332
  type="filepath",
333
  height=600
334
  )
335
 
336
+ # Text display
337
  with gr.Column(scale=1):
338
  text_display = gr.HTML(
339
+ label="Paragraphs with Dates",
340
  value="<div style='padding: 20px; text-align: center; color: #666;'>Upload files to begin</div>"
341
  )
342
 
343
+ # Hidden input for hover
344
+ hover_input = gr.Textbox(visible=False)
 
345
 
346
  # Main load function
347
+ def load_files(pdf_file_obj, json_file_obj):
348
  """Load and process files"""
349
  cleanup_temp_files()
350
 
351
  if not pdf_file_obj or not json_file_obj:
352
+ return [None, None, 1, "1",
353
+ "<div style='color: #666; text-align: center; padding: 20px;'>Please upload both files</div>",
354
+ None, None, None]
 
 
355
 
356
  try:
357
+ pdf_path = pdf_file_obj.name
358
+ json_path = json_file_obj.name
359
+
360
+ print(f"Processing PDF: {pdf_path}")
361
+ print(f"Processing JSON: {json_path}")
362
+
363
  # Annotate PDF
364
  pages, data, total_pages = annotate_pdf_paragraphs(pdf_path, json_path)
365
 
 
378
  # Get text for first page
379
  text_html = get_page_text_html(1)
380
 
381
+ print(f"Successfully loaded: {total_pages} pages")
382
+
383
+ return [pages, data, total_pages, str(total_pages),
384
+ first_page_path, text_html, None, 1]
385
+
386
  except Exception as e:
387
+ error_msg = f"<div style='color: red; padding: 20px;'><b>Error:</b> {str(e)}</div>"
388
+ print(f"Error loading files: {str(e)}")
389
+ return [None, None, 1, "1", None, error_msg, None, 1]
390
 
391
  # Page navigation
392
+ def navigate_page(direction, current_page, total_pages, hover_id):
393
  current_page = int(current_page)
394
  total_pages = int(total_pages)
395
 
 
401
  new_page = current_page
402
 
403
  # Get text for new page
404
+ text_html = get_page_text_html(new_page, hover_id)
405
 
406
  # Update image
407
  if current_display_data and 'pages' in current_display_data:
408
  pages = current_display_data['pages']
409
  if 0 < new_page <= len(pages):
410
+ if hover_id:
411
+ temp_path = update_image_with_hover(new_page, hover_id)
412
+ else:
413
+ temp_path = create_temp_image(pages[new_page - 1])
414
+ return [temp_path, text_html, new_page, new_page, hover_id]
415
 
416
+ return [None, text_html, new_page, new_page, hover_id]
417
 
418
  # Page change
419
+ def change_page(page_num, hover_id):
420
  page_num = int(page_num)
421
+ text_html = get_page_text_html(page_num, hover_id)
422
 
423
  if current_display_data and 'pages' in current_display_data:
424
  pages = current_display_data['pages']
425
  if 0 < page_num <= len(pages):
426
+ if hover_id:
427
+ temp_path = update_image_with_hover(page_num, hover_id)
428
+ else:
429
+ temp_path = create_temp_image(pages[page_num - 1])
430
+ return [temp_path, text_html, page_num, hover_id]
431
 
432
+ return [None, text_html, page_num, hover_id]
433
+
434
+ # Handle hover/click
435
+ def handle_click(para_id, current_page):
436
+ if not para_id:
437
+ # No hover - return current page without highlight
438
+ if current_display_data and 'pages' in current_display_data:
439
+ pages = current_display_data['pages']
440
+ if 0 < current_page <= len(pages):
441
+ temp_path = create_temp_image(pages[current_page - 1])
442
+ text_html = get_page_text_html(current_page)
443
+ return [temp_path, text_html, None]
 
 
 
 
 
 
 
 
 
444
 
445
+ # With highlight
446
+ temp_path = update_image_with_hover(current_page, para_id)
447
+ if temp_path:
448
+ text_html = get_page_text_html(current_page, para_id)
449
+ return [temp_path, text_html, para_id]
450
+
451
+ return [None, get_page_text_html(current_page), para_id]
452
 
453
  # Connect events
454
+ load_btn.click(
455
+ load_files,
456
  inputs=[pdf_file, json_file],
457
  outputs=[
458
  state_pages,
459
  state_data,
460
  state_total_pages,
461
+ total_pages_disp,
462
+ image_display,
 
463
  text_display,
464
+ hover_input,
465
+ state_current_page
466
  ]
467
  )
468
 
469
  next_btn.click(
470
  navigate_page,
471
+ inputs=[gr.Text("next", visible=False), state_current_page, state_total_pages, hover_input],
472
+ outputs=[image_display, text_display, state_current_page, page_num, hover_input]
473
  )
474
 
475
  prev_btn.click(
476
  navigate_page,
477
+ inputs=[gr.Text("prev", visible=False), state_current_page, state_total_pages, hover_input],
478
+ outputs=[image_display, text_display, state_current_page, page_num, hover_input]
479
  )
480
 
481
+ page_num.change(
482
  change_page,
483
+ inputs=[page_num, hover_input],
484
+ outputs=[image_display, text_display, state_current_page, hover_input]
485
  )
486
 
487
+ # Handle text click (hover simulation)
488
  text_display.select(
489
+ lambda evt: (evt.index, 1) if hasattr(evt, 'index') else (None, 1),
490
  None,
491
+ [hover_input, state_current_page]
492
+ ).then(
493
+ handle_click,
494
+ [hover_input, state_current_page],
495
+ [image_display, text_display, hover_input]
496
  )
497
 
498
+ # Add cleanup on exit
499
+ import atexit
500
+ atexit.register(cleanup_temp_files)
501
+
502
  if __name__ == "__main__":
503
+ app.launch(debug=True, server_name="0.0.0.0")