Spaces:
Paused
Paused
Optimize pipeline: load models once per PDF instead of per page for 10x speed improvement
Browse files- pipeline.py +80 -9
- stamp_detector/detect.py +21 -18
pipeline.py
CHANGED
|
@@ -31,6 +31,10 @@ from qr.qr_extraction import process_image_no_save as process_qr
|
|
| 31 |
from signature.inference import detect_signatures
|
| 32 |
from stamp_detector.detect import detect_stamps_no_save
|
| 33 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 34 |
|
| 35 |
def pdf_to_images(pdf_path: str, dpi: int = 200) -> List[np.ndarray]:
|
| 36 |
"""
|
|
@@ -68,13 +72,56 @@ def pdf_to_images(pdf_path: str, dpi: int = 200) -> List[np.ndarray]:
|
|
| 68 |
return images
|
| 69 |
|
| 70 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 71 |
def process_pdf_pipeline(
|
| 72 |
pdf_path: str,
|
| 73 |
output_dir: str = "pipeline_outputs",
|
| 74 |
stamp_model_path: str = "stamp_detector/stamp_model.pt",
|
| 75 |
stamp_conf: float = 0.25,
|
| 76 |
dpi: int = 200,
|
| 77 |
-
save_intermediate: bool = False
|
|
|
|
| 78 |
) -> Dict[str, Any]:
|
| 79 |
"""
|
| 80 |
Process a PDF file by converting each page to an image and running the pipeline.
|
|
@@ -86,6 +133,7 @@ def process_pdf_pipeline(
|
|
| 86 |
stamp_conf: Confidence threshold for stamp detection
|
| 87 |
dpi: DPI for PDF to image conversion
|
| 88 |
save_intermediate: Whether to save intermediate results
|
|
|
|
| 89 |
|
| 90 |
Returns:
|
| 91 |
Combined results dictionary for all pages
|
|
@@ -104,8 +152,17 @@ def process_pdf_pipeline(
|
|
| 104 |
print(f"Processing PDF: {pdf_path.name}")
|
| 105 |
print(f"{'='*70}\n")
|
| 106 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 107 |
# Convert PDF to images
|
| 108 |
-
print(f"π Converting PDF pages to images (DPI: {dpi})...")
|
| 109 |
try:
|
| 110 |
page_images = pdf_to_images(str(pdf_path), dpi=dpi)
|
| 111 |
print(f"β Converted {len(page_images)} page(s) to images\n")
|
|
@@ -126,12 +183,13 @@ def process_pdf_pipeline(
|
|
| 126 |
temp_img_path = temp_dir / f"page_{page_num}.jpg"
|
| 127 |
cv2.imwrite(str(temp_img_path), img)
|
| 128 |
|
| 129 |
-
# Process the page
|
| 130 |
try:
|
| 131 |
page_result = process_image_pipeline(
|
| 132 |
str(temp_img_path),
|
| 133 |
output_dir=output_dir,
|
| 134 |
-
|
|
|
|
| 135 |
stamp_conf=stamp_conf,
|
| 136 |
save_intermediate=save_intermediate
|
| 137 |
)
|
|
@@ -189,7 +247,9 @@ def process_image_pipeline(
|
|
| 189 |
signature_model_path: Optional[str] = None,
|
| 190 |
stamp_model_path: str = "stamp_detector/stamp_model.pt",
|
| 191 |
stamp_conf: float = 0.25,
|
| 192 |
-
save_intermediate: bool = False
|
|
|
|
|
|
|
| 193 |
) -> Dict[str, Any]:
|
| 194 |
"""
|
| 195 |
Process a single image through all three detection models.
|
|
@@ -254,9 +314,16 @@ def process_image_pipeline(
|
|
| 254 |
print(f"\nπ· Step 2/3: Signature Detection")
|
| 255 |
print("-" * 70)
|
| 256 |
try:
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 257 |
sig_result = detect_signatures(
|
| 258 |
str(image_path),
|
| 259 |
-
model=
|
| 260 |
output_dir=None, # Don't save
|
| 261 |
signatures_dir=None, # Don't save
|
| 262 |
save_crops=False # Don't save crops
|
|
@@ -284,13 +351,17 @@ def process_image_pipeline(
|
|
| 284 |
print(f"\nπ· Step 3/3: Stamp Detection")
|
| 285 |
print("-" * 70)
|
| 286 |
try:
|
| 287 |
-
if
|
| 288 |
-
|
|
|
|
|
|
|
|
|
|
| 289 |
|
| 290 |
stamp_result = detect_stamps_no_save(
|
| 291 |
str(image_path),
|
| 292 |
model_path=stamp_model_path,
|
| 293 |
-
conf=stamp_conf
|
|
|
|
| 294 |
)
|
| 295 |
|
| 296 |
if stamp_result and stamp_result.get("detections"):
|
|
|
|
| 31 |
from signature.inference import detect_signatures
|
| 32 |
from stamp_detector.detect import detect_stamps_no_save
|
| 33 |
|
| 34 |
+
# Import for model loading
|
| 35 |
+
from ultralytics import YOLO
|
| 36 |
+
import os
|
| 37 |
+
|
| 38 |
|
| 39 |
def pdf_to_images(pdf_path: str, dpi: int = 200) -> List[np.ndarray]:
|
| 40 |
"""
|
|
|
|
| 72 |
return images
|
| 73 |
|
| 74 |
|
| 75 |
+
def _load_signature_model(signature_model_path: Optional[str] = None):
|
| 76 |
+
"""Load signature model once for reuse."""
|
| 77 |
+
from huggingface_hub import hf_hub_download
|
| 78 |
+
|
| 79 |
+
if signature_model_path and Path(signature_model_path).exists():
|
| 80 |
+
model_path = signature_model_path
|
| 81 |
+
else:
|
| 82 |
+
local_model_path = Path("yolov8s.pt")
|
| 83 |
+
if local_model_path.exists():
|
| 84 |
+
model_path = str(local_model_path)
|
| 85 |
+
else:
|
| 86 |
+
try:
|
| 87 |
+
hf_token = os.environ.get("HF_TOKEN") or os.environ.get("HUGGINGFACE_TOKEN")
|
| 88 |
+
model_path = hf_hub_download(
|
| 89 |
+
repo_id="tech4humans/yolov8s-signature-detector",
|
| 90 |
+
filename="yolov8s.pt",
|
| 91 |
+
token=hf_token
|
| 92 |
+
)
|
| 93 |
+
except Exception as e:
|
| 94 |
+
raise RuntimeError(f"Failed to load signature model: {e}")
|
| 95 |
+
|
| 96 |
+
print("π₯ Loading signature model...")
|
| 97 |
+
model = YOLO(model_path)
|
| 98 |
+
print("β Signature model loaded")
|
| 99 |
+
return model
|
| 100 |
+
|
| 101 |
+
|
| 102 |
+
def _load_stamp_model(stamp_model_path: str = "stamp_detector/stamp_model.pt"):
|
| 103 |
+
"""Load stamp model once for reuse."""
|
| 104 |
+
if not Path(stamp_model_path).exists():
|
| 105 |
+
default_path = Path("stamp_detector/stamp_model.pt")
|
| 106 |
+
if default_path.exists():
|
| 107 |
+
stamp_model_path = str(default_path)
|
| 108 |
+
else:
|
| 109 |
+
raise FileNotFoundError(f"Stamp model not found: {stamp_model_path}")
|
| 110 |
+
|
| 111 |
+
print("π₯ Loading stamp model...")
|
| 112 |
+
model = YOLO(stamp_model_path)
|
| 113 |
+
print("β Stamp model loaded")
|
| 114 |
+
return model
|
| 115 |
+
|
| 116 |
+
|
| 117 |
def process_pdf_pipeline(
|
| 118 |
pdf_path: str,
|
| 119 |
output_dir: str = "pipeline_outputs",
|
| 120 |
stamp_model_path: str = "stamp_detector/stamp_model.pt",
|
| 121 |
stamp_conf: float = 0.25,
|
| 122 |
dpi: int = 200,
|
| 123 |
+
save_intermediate: bool = False,
|
| 124 |
+
signature_model_path: Optional[str] = None
|
| 125 |
) -> Dict[str, Any]:
|
| 126 |
"""
|
| 127 |
Process a PDF file by converting each page to an image and running the pipeline.
|
|
|
|
| 133 |
stamp_conf: Confidence threshold for stamp detection
|
| 134 |
dpi: DPI for PDF to image conversion
|
| 135 |
save_intermediate: Whether to save intermediate results
|
| 136 |
+
signature_model_path: Path to signature model (optional, will auto-download if not provided)
|
| 137 |
|
| 138 |
Returns:
|
| 139 |
Combined results dictionary for all pages
|
|
|
|
| 152 |
print(f"Processing PDF: {pdf_path.name}")
|
| 153 |
print(f"{'='*70}\n")
|
| 154 |
|
| 155 |
+
# Load models once before processing pages
|
| 156 |
+
print("π Loading models (this happens once for all pages)...")
|
| 157 |
+
try:
|
| 158 |
+
signature_model = _load_signature_model(signature_model_path)
|
| 159 |
+
stamp_model = _load_stamp_model(stamp_model_path)
|
| 160 |
+
except Exception as e:
|
| 161 |
+
print(f"β Error loading models: {str(e)}")
|
| 162 |
+
raise
|
| 163 |
+
|
| 164 |
# Convert PDF to images
|
| 165 |
+
print(f"\nπ Converting PDF pages to images (DPI: {dpi})...")
|
| 166 |
try:
|
| 167 |
page_images = pdf_to_images(str(pdf_path), dpi=dpi)
|
| 168 |
print(f"β Converted {len(page_images)} page(s) to images\n")
|
|
|
|
| 183 |
temp_img_path = temp_dir / f"page_{page_num}.jpg"
|
| 184 |
cv2.imwrite(str(temp_img_path), img)
|
| 185 |
|
| 186 |
+
# Process the page with pre-loaded models
|
| 187 |
try:
|
| 188 |
page_result = process_image_pipeline(
|
| 189 |
str(temp_img_path),
|
| 190 |
output_dir=output_dir,
|
| 191 |
+
signature_model=signature_model,
|
| 192 |
+
stamp_model=stamp_model,
|
| 193 |
stamp_conf=stamp_conf,
|
| 194 |
save_intermediate=save_intermediate
|
| 195 |
)
|
|
|
|
| 247 |
signature_model_path: Optional[str] = None,
|
| 248 |
stamp_model_path: str = "stamp_detector/stamp_model.pt",
|
| 249 |
stamp_conf: float = 0.25,
|
| 250 |
+
save_intermediate: bool = False,
|
| 251 |
+
signature_model: Optional[Any] = None,
|
| 252 |
+
stamp_model: Optional[Any] = None
|
| 253 |
) -> Dict[str, Any]:
|
| 254 |
"""
|
| 255 |
Process a single image through all three detection models.
|
|
|
|
| 314 |
print(f"\nπ· Step 2/3: Signature Detection")
|
| 315 |
print("-" * 70)
|
| 316 |
try:
|
| 317 |
+
# Use pre-loaded model if provided, otherwise load on demand
|
| 318 |
+
if signature_model is None:
|
| 319 |
+
if signature_model_path:
|
| 320 |
+
signature_model = _load_signature_model(signature_model_path)
|
| 321 |
+
else:
|
| 322 |
+
signature_model = _load_signature_model()
|
| 323 |
+
|
| 324 |
sig_result = detect_signatures(
|
| 325 |
str(image_path),
|
| 326 |
+
model=signature_model, # Use pre-loaded model
|
| 327 |
output_dir=None, # Don't save
|
| 328 |
signatures_dir=None, # Don't save
|
| 329 |
save_crops=False # Don't save crops
|
|
|
|
| 351 |
print(f"\nπ· Step 3/3: Stamp Detection")
|
| 352 |
print("-" * 70)
|
| 353 |
try:
|
| 354 |
+
# Use pre-loaded model if provided, otherwise load on demand
|
| 355 |
+
if stamp_model is None:
|
| 356 |
+
if not Path(stamp_model_path).exists():
|
| 357 |
+
raise FileNotFoundError(f"Stamp model not found: {stamp_model_path}")
|
| 358 |
+
stamp_model = _load_stamp_model(stamp_model_path)
|
| 359 |
|
| 360 |
stamp_result = detect_stamps_no_save(
|
| 361 |
str(image_path),
|
| 362 |
model_path=stamp_model_path,
|
| 363 |
+
conf=stamp_conf,
|
| 364 |
+
model=stamp_model # Pass pre-loaded model
|
| 365 |
)
|
| 366 |
|
| 367 |
if stamp_result and stamp_result.get("detections"):
|
stamp_detector/detect.py
CHANGED
|
@@ -9,7 +9,7 @@ import json
|
|
| 9 |
from ultralytics import YOLO
|
| 10 |
|
| 11 |
|
| 12 |
-
def detect_stamps_no_save(image_path, model_path="stamp_model.pt", conf=0.25):
|
| 13 |
"""
|
| 14 |
Detect stamps without saving images.
|
| 15 |
|
|
@@ -17,27 +17,30 @@ def detect_stamps_no_save(image_path, model_path="stamp_model.pt", conf=0.25):
|
|
| 17 |
image_path: Path to input image
|
| 18 |
model_path: Path to model (or will download from HF Hub if not found)
|
| 19 |
conf: Confidence threshold
|
|
|
|
| 20 |
|
| 21 |
Returns:
|
| 22 |
dict: Detection results with detections and image_size
|
| 23 |
"""
|
| 24 |
-
#
|
| 25 |
-
if
|
| 26 |
-
#
|
| 27 |
-
|
| 28 |
-
from
|
| 29 |
-
|
| 30 |
-
|
| 31 |
-
|
| 32 |
-
|
| 33 |
-
|
| 34 |
-
|
| 35 |
-
|
| 36 |
-
|
| 37 |
-
|
| 38 |
-
|
| 39 |
-
|
| 40 |
-
|
|
|
|
|
|
|
| 41 |
|
| 42 |
# Load image
|
| 43 |
if not os.path.exists(image_path):
|
|
|
|
| 9 |
from ultralytics import YOLO
|
| 10 |
|
| 11 |
|
| 12 |
+
def detect_stamps_no_save(image_path, model_path="stamp_model.pt", conf=0.25, model=None):
|
| 13 |
"""
|
| 14 |
Detect stamps without saving images.
|
| 15 |
|
|
|
|
| 17 |
image_path: Path to input image
|
| 18 |
model_path: Path to model (or will download from HF Hub if not found)
|
| 19 |
conf: Confidence threshold
|
| 20 |
+
model: Pre-loaded YOLO model (optional, will load if not provided)
|
| 21 |
|
| 22 |
Returns:
|
| 23 |
dict: Detection results with detections and image_size
|
| 24 |
"""
|
| 25 |
+
# Use pre-loaded model if provided, otherwise load model
|
| 26 |
+
if model is None:
|
| 27 |
+
# Load model - try to download from HF Hub if not found locally
|
| 28 |
+
if not os.path.exists(model_path):
|
| 29 |
+
# Try to download from Hugging Face Hub
|
| 30 |
+
try:
|
| 31 |
+
from huggingface_hub import hf_hub_download
|
| 32 |
+
print(f"Model not found locally, attempting to download from HF Hub...")
|
| 33 |
+
# You can upload your model to HF Hub and use it here
|
| 34 |
+
# For now, try the default path in stamp_detector directory
|
| 35 |
+
default_path = os.path.join("stamp_detector", "stamp_model.pt")
|
| 36 |
+
if os.path.exists(default_path):
|
| 37 |
+
model_path = default_path
|
| 38 |
+
else:
|
| 39 |
+
raise FileNotFoundError(f"Stamp model not found: {model_path}. Please upload stamp_model.pt to the Space.")
|
| 40 |
+
except ImportError:
|
| 41 |
+
raise FileNotFoundError(f"Stamp model not found: {model_path}")
|
| 42 |
+
|
| 43 |
+
model = YOLO(model_path)
|
| 44 |
|
| 45 |
# Load image
|
| 46 |
if not os.path.exists(image_path):
|