diff --git a/backend/page_treatment/visualizer.py b/backend/page_treatment/visualizer.py index 20180ee..e10635d 100644 --- a/backend/page_treatment/visualizer.py +++ b/backend/page_treatment/visualizer.py @@ -7,7 +7,7 @@ import argparse import os import re from pathlib import Path -from PIL import Image, ImageDraw +from PIL import Image, ImageDraw, ImageFont # Add parent directory to path for imports import sys @@ -59,92 +59,215 @@ def parse_doctags(doctags_path): doctag_content = doctag_match.group(1) zones = [] - # Find all tags with location information - tag_starts = re.finditer(r'<(\w+)>', doctag_content) + # Define all possible tag types to look for + tag_types = [ + 'section_header_level_1', 'section_header_level_2', 'section_header_level_3', + 'text', 'picture', 'table', 'page_header', 'page_footer', + 'title', 'author', 'abstract', 'keywords', 'paragraph', + 'list_item', 'code_block', 'footnote', 'caption' + ] - for tag_match in tag_starts: - tag_name = tag_match.group(1) + # Find all zones with location information using a more robust pattern + for tag_type in tag_types: + # Pattern to match the complete tag with location data + pattern = rf'<{tag_type}>.*?{LOC_PATTERN}.*?' + matches = re.finditer(pattern, doctag_content, re.DOTALL) + + for match in matches: + full_match = match.group(0) + loc_match = re.search(LOC_PATTERN, full_match) + + if loc_match: + x1, y1, x2, y2 = map(int, loc_match.groups()) + + # Extract text content (remove location tags) + content_start = full_match.find('>') + 1 + content_end = full_match.rfind('', '', content).strip() + + zones.append({ + 'type': tag_type, + 'x1': x1, 'y1': y1, + 'x2': x2, 'y2': y2, + 'content': content + }) + + # Also try a more general pattern for any tags we might have missed + general_pattern = r'<(\w+)>.*?' + LOC_PATTERN + r'.*?' + general_matches = re.finditer(general_pattern, doctag_content, re.DOTALL) + + found_tags = set() + for zone in zones: + found_tags.add(f"{zone['type']}_{zone['x1']}_{zone['y1']}") + + for match in general_matches: + tag_name = match.group(1) if tag_name.startswith('loc_'): continue - tag_start_pos = tag_match.start() - tag_end_pattern = f'' - tag_end_match = re.search(tag_end_pattern, doctag_content[tag_start_pos:]) + x1, y1, x2, y2 = map(int, match.groups()[1:5]) + tag_key = f"{tag_name}_{x1}_{y1}" - if not tag_end_match: - continue - - tag_content = doctag_content[tag_start_pos:tag_start_pos + tag_end_match.end()] - loc_match = re.search(LOC_PATTERN, tag_content) - - if loc_match: - x1, y1, x2, y2 = map(int, loc_match.groups()) - - # Extract text content - content_pattern = f'{LOC_PATTERN}(.*?)' - content_match = re.search(content_pattern, tag_content, re.DOTALL) - text_content = content_match.group(5).strip() if content_match else "" + # Avoid duplicates + if tag_key not in found_tags: + full_match = match.group(0) + content_start = full_match.find('>') + 1 + content_end = full_match.rfind('', '', content).strip() zones.append({ 'type': tag_name, 'x1': x1, 'y1': y1, 'x2': x2, 'y2': y2, - 'content': text_content + 'content': content }) + found_tags.add(tag_key) - # If no zones found, it might be a page with no detectable content + # If no zones found, log the content for debugging if not zones: print(f"Warning: No zones with location data found in {doctags_path}") + print(f"DocTags content preview: {doctag_content[:500]}...") + + # Try to find any loc_ tags to debug + loc_tags = re.findall(r'', doctag_content) + if loc_tags: + print(f"Found {len(loc_tags)} location tags in the file") + else: + print("No location tags found in the file at all") + + # Sort zones by position (top to bottom, left to right) + zones.sort(key=lambda z: (z['y1'], z['x1'])) + + print(f"Parsed {len(zones)} zones from DocTags") + for zone in zones[:5]: # Show first 5 zones for debugging + print(f" - {zone['type']}: ({zone['x1']},{zone['y1']})-({zone['x2']},{zone['y2']})") return zones def create_visualization(image, zones, page_num, output_path): """Create a visualization image with rectangles around zones.""" debug_img = image.copy() - draw = ImageDraw.Draw(debug_img) + draw = ImageDraw.Draw(debug_img, mode='RGBA') # Use RGBA mode for transparency + + print(f"Creating visualization with {len(zones)} zones") + + # Try to use a default font, fallback to PIL default if not available + try: + font = ImageFont.truetype("/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf", 14) + except: + try: + # Try macOS font locations + font = ImageFont.truetype("/System/Library/Fonts/Helvetica.ttc", 14) + except: + try: + # Try Windows font locations + font = ImageFont.truetype("C:\\Windows\\Fonts\\Arial.ttf", 14) + except: + font = ImageFont.load_default() # Draw rectangles for each zone + zone_count = 0 for zone in zones: zone_type = zone['type'] color = ZONE_COLORS.get(zone_type, ZONE_COLORS['default']) - # Draw rectangle + # Ensure coordinates are integers + x1, y1 = int(zone['x1']), int(zone['y1']) + x2, y2 = int(zone['x2']), int(zone['y2']) + + # Skip invalid zones + if x1 >= x2 or y1 >= y2: + print(f"Skipping invalid zone {zone_type}: ({x1},{y1})-({x2},{y2})") + continue + + # Ensure coordinates are within image bounds + x1 = max(0, min(x1, image.width - 1)) + y1 = max(0, min(y1, image.height - 1)) + x2 = max(0, min(x2, image.width)) + y2 = max(0, min(y2, image.height)) + + print(f"Drawing {zone_type} at ({x1},{y1})-({x2},{y2}) with color {color}") + + # Draw rectangle with thicker line draw.rectangle( - [(zone['x1'], zone['y1']), (zone['x2'], zone['y2'])], + [(x1, y1), (x2, y2)], + outline=color, + width=3 # Increased from 2 to make more visible + ) + + # Draw corners for better visibility + corner_length = 10 + corner_width = 4 + # Top-left corner + draw.line([(x1, y1), (x1 + corner_length, y1)], fill=color, width=corner_width) + draw.line([(x1, y1), (x1, y1 + corner_length)], fill=color, width=corner_width) + # Top-right corner + draw.line([(x2 - corner_length, y1), (x2, y1)], fill=color, width=corner_width) + draw.line([(x2, y1), (x2, y1 + corner_length)], fill=color, width=corner_width) + # Bottom-left corner + draw.line([(x1, y2 - corner_length), (x1, y2)], fill=color, width=corner_width) + draw.line([(x1, y2), (x1 + corner_length, y2)], fill=color, width=corner_width) + # Bottom-right corner + draw.line([(x2 - corner_length, y2), (x2, y2)], fill=color, width=corner_width) + draw.line([(x2, y2 - corner_length), (x2, y2)], fill=color, width=corner_width) + + # Add zone type label with better visibility + label_text = zone_type.replace('_', ' ').title() + + # Get text size + text_bbox = draw.textbbox((0, 0), label_text, font=font) + text_width = text_bbox[2] - text_bbox[0] + text_height = text_bbox[3] - text_bbox[1] + + # Position label + label_x = min(x1 + 2, image.width - text_width - 4) + label_y = max(y1 - text_height - 4, 2) + + # Draw label background + draw.rectangle( + [(label_x - 2, label_y - 2), + (label_x + text_width + 2, label_y + text_height + 2)], + fill=(255, 255, 255, 200), outline=color, width=2 ) - # Add zone type label - label_width = len(zone_type) * 7 + 6 - label_x = min(zone['x1'], image.width - label_width) - - draw.rectangle( - [(label_x, zone['y1']), (label_x + label_width, zone['y1'] + 20)], - fill=(255, 255, 255, 180), - outline=color - ) + # Draw label text draw.text( - (label_x + 3, zone['y1'] + 3), - zone_type, - fill=color + (label_x, label_y), + label_text, + fill=color, + font=font ) - # Draw page number + zone_count += 1 + + print(f"Drew {zone_count} zones on the image") + + # Draw page number with better visibility + page_text = f"Page {page_num}" + page_bbox = draw.textbbox((0, 0), page_text, font=font) + page_width = page_bbox[2] - page_bbox[0] + page_height = page_bbox[3] - page_bbox[1] + draw.rectangle( - [(10, 10), (100, 40)], - fill=(0, 0, 0, 180), + [(10, 10), (20 + page_width, 20 + page_height)], + fill=(0, 0, 0, 200), outline=(255, 255, 255) ) draw.text( (15, 15), - f"Page {page_num}", - fill=(255, 255, 255) + page_text, + fill=(255, 255, 255), + font=font ) # Save the image - debug_img.save(output_path) + debug_img.save(output_path, format="PNG") print(f"Visualization saved to: {output_path}") + print(f"Output image size: {debug_img.size}") return debug_img @@ -167,6 +290,12 @@ def process_page(pdf_path, page_num, doctags_path, output_path, dpi, adjust): print(f"Found {len(zones)} zones in DocTags") if zones: + # Debug: print coordinate ranges + x_coords = [zone['x1'] for zone in zones] + [zone['x2'] for zone in zones] + y_coords = [zone['y1'] for zone in zones] + [zone['y2'] for zone in zones] + print(f"Coordinate ranges: X({min(x_coords)}-{max(x_coords)}), Y({min(y_coords)}-{max(y_coords)})") + print(f"Image dimensions: {image.width}x{image.height}") + # Check if we need to adjust coordinates max_x = max([zone['x2'] for zone in zones]) max_y = max([zone['y2'] for zone in zones]) @@ -175,8 +304,26 @@ def process_page(pdf_path, page_num, doctags_path, output_path, dpi, adjust): if max_x <= DEFAULT_GRID_SIZE and max_y <= DEFAULT_GRID_SIZE: print(f"Detected normalized coordinates (0-{DEFAULT_GRID_SIZE} grid)") zones = normalize_coordinates(zones, image.width, image.height) + print(f"After normalization - X range: {min([z['x1'] for z in zones])}-{max([z['x2'] for z in zones])}") elif adjust: + print(f"Applying auto-adjustment (max coords: {max_x}, {max_y})") zones = auto_adjust_coordinates(zones, image.width, image.height) + print(f"After adjustment - X range: {min([z['x1'] for z in zones])}-{max([z['x2'] for z in zones])}") + else: + print("No coordinate adjustment applied") + + # Verify coordinates are within image bounds + out_of_bounds = 0 + for zone in zones: + if (zone['x2'] > image.width or zone['y2'] > image.height or + zone['x1'] < 0 or zone['y1'] < 0): + out_of_bounds += 1 + print(f"Warning: Zone {zone['type']} has out-of-bounds coordinates: " + f"({zone['x1']},{zone['y1']})-({zone['x2']},{zone['y2']})") + + if out_of_bounds > 0: + print(f"Warning: {out_of_bounds} zones have coordinates outside image bounds!") + else: print(f"Warning: No zones found for page {page_num}, creating blank visualization") diff --git a/frontend/index.html b/frontend/index.html index c573a31..72dda17 100644 --- a/frontend/index.html +++ b/frontend/index.html @@ -169,18 +169,6 @@ - - - - - -
- - -