commit
4256fba3a0
3 changed files with 405 additions and 56 deletions
214
backend/app.py
214
backend/app.py
|
|
@ -536,6 +536,220 @@ if batch_processing_available:
|
||||||
state['logs'] = state['logs'][-20:] # Last 20 logs only
|
state['logs'] = state['logs'][-20:] # Last 20 logs only
|
||||||
return jsonify(state)
|
return jsonify(state)
|
||||||
|
|
||||||
|
|
||||||
|
# Add these routes to your app.py file after the other batch processing endpoints
|
||||||
|
|
||||||
|
@app.route('/batch-report/<batch_id>')
|
||||||
|
def batch_report(batch_id):
|
||||||
|
"""Serve the batch processing report HTML"""
|
||||||
|
try:
|
||||||
|
processor = get_batch_processor(batch_id)
|
||||||
|
if not processor:
|
||||||
|
# Try to find existing report even if processor is gone
|
||||||
|
report_path = ensure_results_folder() / f"batch_{batch_id}" / "report.html"
|
||||||
|
if report_path.exists():
|
||||||
|
return send_file(report_path)
|
||||||
|
else:
|
||||||
|
return jsonify({'error': 'Batch report not found'}), 404
|
||||||
|
|
||||||
|
# Check if report exists
|
||||||
|
report_path = processor.results_dir / "report.html"
|
||||||
|
if not report_path.exists():
|
||||||
|
# Generate report if it doesn't exist
|
||||||
|
processor.generate_report()
|
||||||
|
|
||||||
|
return send_file(report_path)
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Error serving batch report: {e}")
|
||||||
|
return jsonify({'error': str(e)}), 500
|
||||||
|
|
||||||
|
@app.route('/batch-report-image/<batch_id>/<image_name>')
|
||||||
|
def batch_report_image(batch_id, image_name):
|
||||||
|
"""Serve images from batch report directory"""
|
||||||
|
try:
|
||||||
|
image_path = ensure_results_folder() / f"batch_{batch_id}" / image_name
|
||||||
|
|
||||||
|
if not image_path.exists():
|
||||||
|
return jsonify({'error': 'Image not found'}), 404
|
||||||
|
|
||||||
|
return send_file(image_path)
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Error serving batch image: {e}")
|
||||||
|
return jsonify({'error': str(e)}), 500
|
||||||
|
|
||||||
|
@app.route('/pause-batch/<batch_id>', methods=['POST'])
|
||||||
|
def pause_batch(batch_id):
|
||||||
|
"""Pause a batch processing job"""
|
||||||
|
try:
|
||||||
|
processor = get_batch_processor(batch_id)
|
||||||
|
if not processor:
|
||||||
|
return jsonify({'error': 'Batch not found'}), 404
|
||||||
|
|
||||||
|
processor.pause()
|
||||||
|
return jsonify({'success': True})
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Error pausing batch: {e}")
|
||||||
|
return jsonify({'error': str(e)}), 500
|
||||||
|
|
||||||
|
@app.route('/resume-batch/<batch_id>', methods=['POST'])
|
||||||
|
def resume_batch(batch_id):
|
||||||
|
"""Resume a batch processing job"""
|
||||||
|
try:
|
||||||
|
processor = get_batch_processor(batch_id)
|
||||||
|
if not processor:
|
||||||
|
return jsonify({'error': 'Batch not found'}), 404
|
||||||
|
|
||||||
|
processor.resume()
|
||||||
|
return jsonify({'success': True})
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Error resuming batch: {e}")
|
||||||
|
return jsonify({'error': str(e)}), 500
|
||||||
|
|
||||||
|
@app.route('/cancel-batch/<batch_id>', methods=['POST'])
|
||||||
|
def cancel_batch(batch_id):
|
||||||
|
"""Cancel a batch processing job"""
|
||||||
|
try:
|
||||||
|
processor = get_batch_processor(batch_id)
|
||||||
|
if not processor:
|
||||||
|
return jsonify({'error': 'Batch not found'}), 404
|
||||||
|
|
||||||
|
processor.cancel()
|
||||||
|
return jsonify({'success': True})
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Error cancelling batch: {e}")
|
||||||
|
return jsonify({'error': str(e)}), 500
|
||||||
|
|
||||||
|
@app.route('/download-batch-results/<batch_id>')
|
||||||
|
def download_batch_results(batch_id):
|
||||||
|
"""Download batch results as ZIP"""
|
||||||
|
try:
|
||||||
|
processor = get_batch_processor(batch_id)
|
||||||
|
if not processor:
|
||||||
|
# Try to find existing results
|
||||||
|
batch_dir = ensure_results_folder() / f"batch_{batch_id}"
|
||||||
|
if not batch_dir.exists():
|
||||||
|
return jsonify({'error': 'Batch results not found'}), 404
|
||||||
|
|
||||||
|
# Create a temporary processor just for zipping
|
||||||
|
class TempProcessor:
|
||||||
|
def __init__(self, batch_id):
|
||||||
|
self.batch_id = batch_id
|
||||||
|
self.results_dir = batch_dir
|
||||||
|
|
||||||
|
def create_zip_archive(self):
|
||||||
|
import zipfile
|
||||||
|
zip_path = self.results_dir / f"batch_results_{self.batch_id}.zip"
|
||||||
|
with zipfile.ZipFile(zip_path, 'w', zipfile.ZIP_DEFLATED) as zipf:
|
||||||
|
for file_path in self.results_dir.rglob('*'):
|
||||||
|
if file_path.is_file() and file_path != zip_path:
|
||||||
|
arcname = file_path.relative_to(self.results_dir)
|
||||||
|
zipf.write(file_path, arcname)
|
||||||
|
return zip_path
|
||||||
|
|
||||||
|
processor = TempProcessor(batch_id)
|
||||||
|
|
||||||
|
# Create ZIP archive
|
||||||
|
zip_path = processor.create_zip_archive()
|
||||||
|
if not zip_path or not zip_path.exists():
|
||||||
|
return jsonify({'error': 'Failed to create ZIP archive'}), 500
|
||||||
|
|
||||||
|
return send_file(
|
||||||
|
zip_path,
|
||||||
|
as_attachment=True,
|
||||||
|
download_name=f"doctags_batch_{batch_id}.zip"
|
||||||
|
)
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Error downloading batch results: {e}")
|
||||||
|
return jsonify({'error': str(e)}), 500
|
||||||
|
|
||||||
|
@app.route('/retry-page', methods=['POST'])
|
||||||
|
def retry_page():
|
||||||
|
"""Retry processing a failed page"""
|
||||||
|
try:
|
||||||
|
pdf_file = request.form.get('pdf_file')
|
||||||
|
page_num = int(request.form.get('page_num'))
|
||||||
|
adjust = request.form.get('adjust') == 'true'
|
||||||
|
|
||||||
|
if not pdf_file or not os.path.exists(pdf_file):
|
||||||
|
return jsonify({'success': False, 'error': 'Invalid PDF file'}), 400
|
||||||
|
|
||||||
|
# Run the three processing steps
|
||||||
|
results = {'success': True, 'errors': []}
|
||||||
|
|
||||||
|
# Step 1: Analyzer
|
||||||
|
command = (f"python backend/page_treatment/analyzer.py --image {pdf_file} "
|
||||||
|
f"--page {page_num} --start-page {page_num} --end-page {page_num}")
|
||||||
|
success, stdout, stderr = run_command_with_timeout(command, 60)
|
||||||
|
if not success:
|
||||||
|
results['errors'].append(f"Analyzer: {stderr}")
|
||||||
|
results['success'] = False
|
||||||
|
|
||||||
|
# Step 2: Visualizer (only if analyzer succeeded)
|
||||||
|
if results['success']:
|
||||||
|
command = (f"python backend/page_treatment/visualizer.py "
|
||||||
|
f"--doctags results/output.doctags.txt --pdf {pdf_file} --page {page_num}")
|
||||||
|
if adjust:
|
||||||
|
command += " --adjust"
|
||||||
|
success, stdout, stderr = run_command_with_timeout(command, 60)
|
||||||
|
if not success:
|
||||||
|
results['errors'].append(f"Visualizer: {stderr}")
|
||||||
|
results['success'] = False
|
||||||
|
|
||||||
|
# Step 3: Extractor (only if previous steps succeeded)
|
||||||
|
if results['success']:
|
||||||
|
command = (f"python backend/page_treatment/picture_extractor.py "
|
||||||
|
f"--doctags results/output.doctags.txt --pdf {pdf_file} --page {page_num}")
|
||||||
|
if adjust:
|
||||||
|
command += " --adjust"
|
||||||
|
success, stdout, stderr = run_command_with_timeout(command, 60)
|
||||||
|
if not success:
|
||||||
|
results['errors'].append(f"Extractor: {stderr}")
|
||||||
|
# Don't fail completely if just extractor fails
|
||||||
|
|
||||||
|
if results['success']:
|
||||||
|
return jsonify({'success': True})
|
||||||
|
else:
|
||||||
|
return jsonify({
|
||||||
|
'success': False,
|
||||||
|
'error': '; '.join(results['errors'])
|
||||||
|
})
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Error retrying page: {e}")
|
||||||
|
return jsonify({'success': False, 'error': str(e)}), 500
|
||||||
|
|
||||||
|
@app.route('/open-results-folder', methods=['POST'])
|
||||||
|
def open_results_folder():
|
||||||
|
"""Open the results folder in the system file explorer"""
|
||||||
|
try:
|
||||||
|
import platform
|
||||||
|
import subprocess
|
||||||
|
|
||||||
|
results_dir = ensure_results_folder()
|
||||||
|
|
||||||
|
if platform.system() == 'Windows':
|
||||||
|
os.startfile(str(results_dir))
|
||||||
|
elif platform.system() == 'Darwin': # macOS
|
||||||
|
subprocess.Popen(['open', str(results_dir)])
|
||||||
|
else: # Linux and others
|
||||||
|
subprocess.Popen(['xdg-open', str(results_dir)])
|
||||||
|
|
||||||
|
return jsonify({'success': True})
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Error opening results folder: {e}")
|
||||||
|
return jsonify({
|
||||||
|
'success': False,
|
||||||
|
'error': 'Could not open folder automatically. ' +
|
||||||
|
f'Please navigate to: {ensure_results_folder()}'
|
||||||
|
})
|
||||||
|
|
||||||
# API endpoints for file upload
|
# API endpoints for file upload
|
||||||
@app.route('/api/upload/doctags', methods=['POST'])
|
@app.route('/api/upload/doctags', methods=['POST'])
|
||||||
def api_upload_doctags():
|
def api_upload_doctags():
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,7 @@ import argparse
|
||||||
import os
|
import os
|
||||||
import re
|
import re
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from PIL import Image, ImageDraw
|
from PIL import Image, ImageDraw, ImageFont
|
||||||
|
|
||||||
# Add parent directory to path for imports
|
# Add parent directory to path for imports
|
||||||
import sys
|
import sys
|
||||||
|
|
@ -59,92 +59,215 @@ def parse_doctags(doctags_path):
|
||||||
doctag_content = doctag_match.group(1)
|
doctag_content = doctag_match.group(1)
|
||||||
zones = []
|
zones = []
|
||||||
|
|
||||||
# Find all tags with location information
|
# Define all possible tag types to look for
|
||||||
tag_starts = re.finditer(r'<(\w+)>', doctag_content)
|
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:
|
# Find all zones with location information using a more robust pattern
|
||||||
tag_name = tag_match.group(1)
|
for tag_type in tag_types:
|
||||||
|
# Pattern to match the complete tag with location data
|
||||||
|
pattern = rf'<{tag_type}>.*?{LOC_PATTERN}.*?</{tag_type}>'
|
||||||
|
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 = full_match[content_start:content_end]
|
||||||
|
content = re.sub(r'<loc_\d+>', '', 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'.*?</\1>'
|
||||||
|
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_'):
|
if tag_name.startswith('loc_'):
|
||||||
continue
|
continue
|
||||||
|
|
||||||
tag_start_pos = tag_match.start()
|
x1, y1, x2, y2 = map(int, match.groups()[1:5])
|
||||||
tag_end_pattern = f'</({tag_name})>'
|
tag_key = f"{tag_name}_{x1}_{y1}"
|
||||||
tag_end_match = re.search(tag_end_pattern, doctag_content[tag_start_pos:])
|
|
||||||
|
|
||||||
if not tag_end_match:
|
# Avoid duplicates
|
||||||
continue
|
if tag_key not in found_tags:
|
||||||
|
full_match = match.group(0)
|
||||||
tag_content = doctag_content[tag_start_pos:tag_start_pos + tag_end_match.end()]
|
content_start = full_match.find('>') + 1
|
||||||
loc_match = re.search(LOC_PATTERN, tag_content)
|
content_end = full_match.rfind('</')
|
||||||
|
content = full_match[content_start:content_end]
|
||||||
if loc_match:
|
content = re.sub(r'<loc_\d+>', '', content).strip()
|
||||||
x1, y1, x2, y2 = map(int, loc_match.groups())
|
|
||||||
|
|
||||||
# Extract text content
|
|
||||||
content_pattern = f'{LOC_PATTERN}(.*?)</{tag_name}>'
|
|
||||||
content_match = re.search(content_pattern, tag_content, re.DOTALL)
|
|
||||||
text_content = content_match.group(5).strip() if content_match else ""
|
|
||||||
|
|
||||||
zones.append({
|
zones.append({
|
||||||
'type': tag_name,
|
'type': tag_name,
|
||||||
'x1': x1, 'y1': y1,
|
'x1': x1, 'y1': y1,
|
||||||
'x2': x2, 'y2': y2,
|
'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:
|
if not zones:
|
||||||
print(f"Warning: No zones with location data found in {doctags_path}")
|
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'<loc_\d+>', 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
|
return zones
|
||||||
|
|
||||||
def create_visualization(image, zones, page_num, output_path):
|
def create_visualization(image, zones, page_num, output_path):
|
||||||
"""Create a visualization image with rectangles around zones."""
|
"""Create a visualization image with rectangles around zones."""
|
||||||
debug_img = image.copy()
|
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
|
# Draw rectangles for each zone
|
||||||
|
zone_count = 0
|
||||||
for zone in zones:
|
for zone in zones:
|
||||||
zone_type = zone['type']
|
zone_type = zone['type']
|
||||||
color = ZONE_COLORS.get(zone_type, ZONE_COLORS['default'])
|
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(
|
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,
|
outline=color,
|
||||||
width=2
|
width=2
|
||||||
)
|
)
|
||||||
|
|
||||||
# Add zone type label
|
# Draw label text
|
||||||
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.text(
|
draw.text(
|
||||||
(label_x + 3, zone['y1'] + 3),
|
(label_x, label_y),
|
||||||
zone_type,
|
label_text,
|
||||||
fill=color
|
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(
|
draw.rectangle(
|
||||||
[(10, 10), (100, 40)],
|
[(10, 10), (20 + page_width, 20 + page_height)],
|
||||||
fill=(0, 0, 0, 180),
|
fill=(0, 0, 0, 200),
|
||||||
outline=(255, 255, 255)
|
outline=(255, 255, 255)
|
||||||
)
|
)
|
||||||
draw.text(
|
draw.text(
|
||||||
(15, 15),
|
(15, 15),
|
||||||
f"Page {page_num}",
|
page_text,
|
||||||
fill=(255, 255, 255)
|
fill=(255, 255, 255),
|
||||||
|
font=font
|
||||||
)
|
)
|
||||||
|
|
||||||
# Save the image
|
# Save the image
|
||||||
debug_img.save(output_path)
|
debug_img.save(output_path, format="PNG")
|
||||||
print(f"Visualization saved to: {output_path}")
|
print(f"Visualization saved to: {output_path}")
|
||||||
|
print(f"Output image size: {debug_img.size}")
|
||||||
|
|
||||||
return debug_img
|
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")
|
print(f"Found {len(zones)} zones in DocTags")
|
||||||
|
|
||||||
if zones:
|
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
|
# Check if we need to adjust coordinates
|
||||||
max_x = max([zone['x2'] for zone in zones])
|
max_x = max([zone['x2'] for zone in zones])
|
||||||
max_y = max([zone['y2'] 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:
|
if max_x <= DEFAULT_GRID_SIZE and max_y <= DEFAULT_GRID_SIZE:
|
||||||
print(f"Detected normalized coordinates (0-{DEFAULT_GRID_SIZE} grid)")
|
print(f"Detected normalized coordinates (0-{DEFAULT_GRID_SIZE} grid)")
|
||||||
zones = normalize_coordinates(zones, image.width, image.height)
|
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:
|
elif adjust:
|
||||||
|
print(f"Applying auto-adjustment (max coords: {max_x}, {max_y})")
|
||||||
zones = auto_adjust_coordinates(zones, image.width, image.height)
|
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:
|
else:
|
||||||
print(f"Warning: No zones found for page {page_num}, creating blank visualization")
|
print(f"Warning: No zones found for page {page_num}, creating blank visualization")
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -169,18 +169,6 @@
|
||||||
|
|
||||||
<!-- Output Section -->
|
<!-- Output Section -->
|
||||||
<div id="output" class="output hidden"></div>
|
<div id="output" class="output hidden"></div>
|
||||||
|
|
||||||
<!-- Environment Check -->
|
|
||||||
<div id="environment-check" class="hidden">
|
|
||||||
<h3>System Environment</h3>
|
|
||||||
<div id="env-details"></div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Debug Section -->
|
|
||||||
<div class="debug-section">
|
|
||||||
<button onclick="checkEnvironment()">System Check</button>
|
|
||||||
<button onclick="manuallyRunScript()">Manual Command</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<script src="static/app.js"></script>
|
<script src="static/app.js"></script>
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue