This commit is contained in:
+78
-22
@@ -1,8 +1,11 @@
|
|||||||
import os
|
import os
|
||||||
import tempfile
|
import tempfile
|
||||||
import logging
|
import logging
|
||||||
|
import asyncio
|
||||||
from typing import List, Optional
|
from typing import List, Optional
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
from concurrent.futures import ThreadPoolExecutor
|
||||||
|
import time
|
||||||
|
|
||||||
from fastapi import FastAPI, File, UploadFile, HTTPException, BackgroundTasks
|
from fastapi import FastAPI, File, UploadFile, HTTPException, BackgroundTasks
|
||||||
from fastapi.responses import JSONResponse
|
from fastapi.responses import JSONResponse
|
||||||
@@ -40,11 +43,51 @@ async def root():
|
|||||||
async def health_check():
|
async def health_check():
|
||||||
return {"status": "healthy", "service": "OCR API"}
|
return {"status": "healthy", "service": "OCR API"}
|
||||||
|
|
||||||
async def process_pdf_with_ocr(pdf_path: str) -> OCRResponse:
|
def process_single_page(args):
|
||||||
"""
|
"""
|
||||||
Process a PDF file and extract text using OCR
|
Process a single page with OCR (runs in thread pool)
|
||||||
|
"""
|
||||||
|
page_num, image = args
|
||||||
|
try:
|
||||||
|
logger.info(f"Processing page {page_num + 1}")
|
||||||
|
|
||||||
|
# Perform OCR on the image
|
||||||
|
ocr_data = pytesseract.image_to_data(image, output_type=pytesseract.Output.DICT)
|
||||||
|
|
||||||
|
# Extract text and confidence scores
|
||||||
|
page_text = []
|
||||||
|
total_confidence = 0
|
||||||
|
confidence_count = 0
|
||||||
|
|
||||||
|
for j in range(len(ocr_data['text'])):
|
||||||
|
if int(ocr_data['conf'][j]) > 0: # Filter out low confidence results
|
||||||
|
page_text.append(ocr_data['text'][j])
|
||||||
|
total_confidence += float(ocr_data['conf'][j])
|
||||||
|
confidence_count += 1
|
||||||
|
|
||||||
|
page_text = ' '.join(page_text).strip()
|
||||||
|
avg_confidence = total_confidence / confidence_count if confidence_count > 0 else 0
|
||||||
|
|
||||||
|
return {
|
||||||
|
'page_num': page_num,
|
||||||
|
'text': page_text,
|
||||||
|
'confidence': avg_confidence,
|
||||||
|
'success': True
|
||||||
|
}
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Error processing page {page_num + 1}: {str(e)}")
|
||||||
|
return {
|
||||||
|
'page_num': page_num,
|
||||||
|
'text': '',
|
||||||
|
'confidence': 0,
|
||||||
|
'success': False,
|
||||||
|
'error': str(e)
|
||||||
|
}
|
||||||
|
|
||||||
|
async def process_pdf_with_ocr(pdf_path: str, max_workers: int = None) -> OCRResponse:
|
||||||
|
"""
|
||||||
|
Process a PDF file and extract text using OCR with concurrent page processing
|
||||||
"""
|
"""
|
||||||
import time
|
|
||||||
start_time = time.time()
|
start_time = time.time()
|
||||||
|
|
||||||
try:
|
try:
|
||||||
@@ -55,34 +98,45 @@ async def process_pdf_with_ocr(pdf_path: str) -> OCRResponse:
|
|||||||
if not images:
|
if not images:
|
||||||
raise HTTPException(status_code=400, detail="Could not extract images from PDF")
|
raise HTTPException(status_code=400, detail="Could not extract images from PDF")
|
||||||
|
|
||||||
# Extract text from each page
|
# Determine optimal number of workers
|
||||||
|
if max_workers is None:
|
||||||
|
max_workers = min(len(images), os.cpu_count() or 4)
|
||||||
|
|
||||||
|
logger.info(f"Processing {len(images)} pages with {max_workers} workers")
|
||||||
|
|
||||||
|
# Process pages concurrently using ThreadPoolExecutor
|
||||||
|
with ThreadPoolExecutor(max_workers=max_workers) as executor:
|
||||||
|
# Create tasks for each page
|
||||||
|
page_tasks = [(i, image) for i, image in enumerate(images)]
|
||||||
|
|
||||||
|
# Submit all tasks and wait for completion
|
||||||
|
results = list(executor.map(process_single_page, page_tasks))
|
||||||
|
|
||||||
|
# Sort results by page number and combine
|
||||||
|
results.sort(key=lambda x: x['page_num'])
|
||||||
|
|
||||||
all_text = []
|
all_text = []
|
||||||
total_confidence = 0
|
total_confidence = 0
|
||||||
confidence_count = 0
|
confidence_count = 0
|
||||||
|
successful_pages = 0
|
||||||
|
|
||||||
for i, image in enumerate(images):
|
for result in results:
|
||||||
logger.info(f"Processing page {i+1}/{len(images)}")
|
if result['success']:
|
||||||
|
successful_pages += 1
|
||||||
# Perform OCR on the image
|
if result['text']:
|
||||||
ocr_data = pytesseract.image_to_data(image, output_type=pytesseract.Output.DICT)
|
all_text.append(f"--- Page {result['page_num'] + 1} ---\n{result['text']}")
|
||||||
|
total_confidence += result['confidence']
|
||||||
# Extract text and confidence scores
|
|
||||||
page_text = []
|
|
||||||
for j in range(len(ocr_data['text'])):
|
|
||||||
if int(ocr_data['conf'][j]) > 0: # Filter out low confidence results
|
|
||||||
page_text.append(ocr_data['text'][j])
|
|
||||||
total_confidence += float(ocr_data['conf'][j])
|
|
||||||
confidence_count += 1
|
confidence_count += 1
|
||||||
|
else:
|
||||||
page_text = ' '.join(page_text).strip()
|
logger.warning(f"Page {result['page_num'] + 1} failed: {result.get('error', 'Unknown error')}")
|
||||||
if page_text:
|
|
||||||
all_text.append(f"--- Page {i+1} ---\n{page_text}")
|
|
||||||
|
|
||||||
# Calculate average confidence
|
# Calculate average confidence
|
||||||
avg_confidence = total_confidence / confidence_count if confidence_count > 0 else None
|
avg_confidence = total_confidence / confidence_count if confidence_count > 0 else None
|
||||||
|
|
||||||
processing_time = time.time() - start_time
|
processing_time = time.time() - start_time
|
||||||
|
|
||||||
|
logger.info(f"Completed processing {successful_pages}/{len(images)} pages in {processing_time:.2f}s")
|
||||||
|
|
||||||
return OCRResponse(
|
return OCRResponse(
|
||||||
text='\n\n'.join(all_text),
|
text='\n\n'.join(all_text),
|
||||||
pages=len(images),
|
pages=len(images),
|
||||||
@@ -97,12 +151,14 @@ async def process_pdf_with_ocr(pdf_path: str) -> OCRResponse:
|
|||||||
@app.post("/ocr/pdf", response_model=OCRResponse)
|
@app.post("/ocr/pdf", response_model=OCRResponse)
|
||||||
async def extract_text_from_pdf(
|
async def extract_text_from_pdf(
|
||||||
file: UploadFile = File(...),
|
file: UploadFile = File(...),
|
||||||
|
max_workers: int = None,
|
||||||
background_tasks: BackgroundTasks = None
|
background_tasks: BackgroundTasks = None
|
||||||
):
|
):
|
||||||
"""
|
"""
|
||||||
Extract text from a PDF file using OCR
|
Extract text from a PDF file using OCR with concurrent processing
|
||||||
|
|
||||||
- **file**: PDF file to process
|
- **file**: PDF file to process
|
||||||
|
- **max_workers**: Maximum number of concurrent workers (default: min(pages, CPU cores))
|
||||||
- Returns extracted text with metadata
|
- Returns extracted text with metadata
|
||||||
"""
|
"""
|
||||||
|
|
||||||
@@ -130,7 +186,7 @@ async def extract_text_from_pdf(
|
|||||||
|
|
||||||
try:
|
try:
|
||||||
# Process the PDF
|
# Process the PDF
|
||||||
result = await process_pdf_with_ocr(temp_file_path)
|
result = await process_pdf_with_ocr(temp_file_path, max_workers)
|
||||||
|
|
||||||
# Clean up temporary file
|
# Clean up temporary file
|
||||||
background_tasks.add_task(os.unlink, temp_file_path)
|
background_tasks.add_task(os.unlink, temp_file_path)
|
||||||
|
|||||||
Reference in New Issue
Block a user