doc-oc / scripts /predict_table.py
Adityakeerti
Fix: cache transformer model to prevent reload on every table detection call
abdcb54
Raw History Blame Contribute Delete
14.4 kB
"""Table Detection Inference Script"""
import os
import torch
import matplotlib
matplotlib.use('Agg')
import matplotlib.pyplot as plt
import matplotlib.patches as patches
from PIL import Image, ImageOps
from transformers import AutoImageProcessor, TableTransformerForObjectDetection
import numpy as np
import argparse
# Module-level cache: avoids reloading the heavy transformer model on every call.
# Key: model_path string → Value: (processor, model) tuple
_MODEL_CACHE: dict = {}
def load_model(model_path="models\tt_finetuned"):
"""Load the fine-tuned model or use pretrained model.
Results are cached by model_path so the weights are only read from disk
once per process, preventing repeated large memory allocations.
"""
if model_path in _MODEL_CACHE:
return _MODEL_CACHE[model_path]
print(f"Loading model from: {model_path}")
try:
processor = AutoImageProcessor.from_pretrained(model_path)
model = TableTransformerForObjectDetection.from_pretrained(model_path)
print("Fine-tuned model loaded successfully!")
except Exception as e:
print(f"Could not load fine-tuned model: {e}")
print("Falling back to pretrained model...")
processor = AutoImageProcessor.from_pretrained("microsoft/table-transformer-detection")
model = TableTransformerForObjectDetection.from_pretrained("microsoft/table-transformer-detection")
print("Pretrained model loaded successfully!")
_MODEL_CACHE[model_path] = (processor, model)
return processor, model
def detect_tables(image_path, processor, model, confidence_threshold=0.5, info_threshold=0.5, marks_threshold=0.8, fix_orientation=True):
"""Detect tables in an image"""
print(f"Processing image: {image_path}")
# Load and preprocess image
image = Image.open(image_path)
if fix_orientation:
image = ImageOps.exif_transpose(image)
image = image.convert("RGB")
# Process image
inputs = processor(images=image, return_tensors="pt")
# Run inference
with torch.no_grad():
outputs = model(**inputs)
# Post-process results
target_sizes = torch.tensor([image.size[::-1]]) # [height, width]
# Use the minimum of thresholds for initial decoding, then filter per-class below
decode_threshold = confidence_threshold
if info_threshold is not None:
decode_threshold = min(decode_threshold, info_threshold)
if marks_threshold is not None:
decode_threshold = min(decode_threshold, marks_threshold)
results = processor.post_process_object_detection(
outputs,
target_sizes=target_sizes,
threshold=decode_threshold
)[0]
# Optional per-class thresholding
if info_threshold is not None or marks_threshold is not None:
scores = results["scores"]
labels = results["labels"]
boxes = results["boxes"]
keep_indices = []
for i in range(len(scores)):
label_id = labels[i].item()
score_val = scores[i].item()
thr = info_threshold if label_id == 0 else marks_threshold if label_id == 1 else confidence_threshold
if thr is None:
thr = confidence_threshold
if score_val >= thr:
keep_indices.append(i)
if len(keep_indices) != len(scores):
idx = torch.tensor(keep_indices, dtype=torch.long)
results = {
"scores": scores.index_select(0, idx),
"labels": labels.index_select(0, idx),
"boxes": boxes.index_select(0, idx),
}
return image, results
def detect_tables_with_boxes_and_scores(image_path, model_path="models\tt_finetuned", confidence_threshold=0.5, info_threshold=0.5, marks_threshold=0.8, fix_orientation=True):
"""Detect tables in an image and return bounding boxes with confidence scores"""
processor, model = load_model(model_path)
image, results = detect_tables(image_path, processor, model, confidence_threshold, info_threshold, marks_threshold, fix_orientation)
boxes_with_labels_and_scores = []
for i, (score, label, box) in enumerate(zip(results["scores"], results["labels"], results["boxes"])):
x0, y0, x1, y1 = box.tolist()
label_id = label.item()
confidence_score = score.item()
boxes_with_labels_and_scores.append(([x0, y0, x1, y1], label_id, confidence_score))
return boxes_with_labels_and_scores
def detect_tables_with_boxes(image_path, model_path="models\tt_finetuned", confidence_threshold=0.5, info_threshold=0.5, marks_threshold=0.8, fix_orientation=True):
"""Detect tables in an image and return bounding boxes"""
processor, model = load_model(model_path)
image, results = detect_tables(image_path, processor, model, confidence_threshold, info_threshold, marks_threshold, fix_orientation)
boxes_with_labels = []
for i, (score, label, box) in enumerate(zip(results["scores"], results["labels"], results["boxes"])):
x0, y0, x1, y1 = box.tolist()
label_id = label.item()
boxes_with_labels.append(([x0, y0, x1, y1], label_id))
return boxes_with_labels
def visualize_results(image, results, save_path=None, show_plot=False):
"""Visualize detection results"""
fig, ax = plt.subplots(1, 1, figsize=(12, 8))
ax.imshow(image)
ax.set_title("Table Detection Results", fontsize=16)
# Define colors and labels for different table types
table_colors = {
0: 'red', # information_table
1: 'blue' # marks_table
}
table_labels = {
0: "Information Table",
1: "Marks Table"
}
for i, (score, label, box) in enumerate(zip(results["scores"], results["labels"], results["boxes"])):
x0, y0, x1, y1 = box.tolist()
width = x1 - x0
height = y1 - y0
# Get color and label for this table type
label_id = label.item()
color = table_colors.get(label_id, 'green')
table_type = table_labels.get(label_id, f"Table {label_id}")
# Create rectangle
rect = patches.Rectangle(
(x0, y0), width, height,
linewidth=2, edgecolor=color, facecolor='none'
)
ax.add_patch(rect)
# Add label with table type
label_text = f"{table_type}: {score:.2f}"
ax.text(x0, y0-5, label_text,
color=color, fontsize=10, weight='bold',
bbox=dict(boxstyle="round,pad=0.3", facecolor='white', alpha=0.8))
ax.axis("off")
if save_path:
plt.savefig(save_path, dpi=300, bbox_inches='tight')
print(f"Results saved to: {save_path}")
# Removed plt.show() to prevent popup
plt.close(fig) # Always close to release memory; figures accumulate otherwise.
return fig
def process_single_image(image_path, model_path="models\\tt_finetuned", confidence_threshold=0.5, info_threshold=0.5, marks_threshold=0.8, save_results=True, fix_orientation=True):
"""Process a single image"""
# Load model
processor, model = load_model(model_path)
# Detect tables
image, results = detect_tables(
image_path, processor, model,
confidence_threshold=confidence_threshold,
info_threshold=info_threshold,
marks_threshold=marks_threshold,
fix_orientation=fix_orientation,
)
# Print results
print(f"\nDetection Results:")
print(f"Found {len(results['scores'])} tables")
table_counts = {0: 0, 1: 0} # Count by table type
for i, (score, label, box) in enumerate(zip(results["scores"], results["labels"], results["boxes"])):
x0, y0, x1, y1 = box.tolist()
label_id = label.item()
table_counts[label_id] += 1
table_type = "Information Table" if label_id == 0 else "Marks Table"
print(f" {table_type} {table_counts[label_id]}: confidence={score:.3f}, bbox=({x0:.0f}, {y0:.0f}, {x1:.0f}, {y1:.0f})")
print(f"\nSummary: {table_counts[0]} Information Tables, {table_counts[1]} Marks Tables")
# Visualize results
if save_results:
base_name = os.path.splitext(os.path.basename(image_path))[0]
save_path = f"{base_name}_detection_results.png"
else:
save_path = None
visualize_results(image, results, save_path=save_path)
image.close() # Release PIL image buffer; no longer needed after visualisation.
# Return 1 if tables found, 0 if none
return 1 if len(results['scores']) > 0 else 0
def process_batch_images(image_dir, model_path="models\tt_finetuned", confidence_threshold=0.5, info_threshold=0.5, marks_threshold=0.8, fix_orientation=True):
"""Process all images in a directory"""
# Load model
processor, model = load_model(model_path)
# Get all image files
image_extensions = ('.jpg', '.jpeg', '.png', '.bmp', '.tiff')
image_files = [f for f in os.listdir(image_dir)
if f.lower().endswith(image_extensions)]
if not image_files:
print(f"No images found in {image_dir}")
return
print(f"Processing {len(image_files)} images...")
results_summary = []
for i, image_file in enumerate(image_files):
print(f"\n--- Processing {i+1}/{len(image_files)}: {image_file} ---")
image_path = os.path.join(image_dir, image_file)
try:
# Detect tables
image, results = detect_tables(
image_path, processor, model,
confidence_threshold=confidence_threshold,
info_threshold=info_threshold,
marks_threshold=marks_threshold,
fix_orientation=fix_orientation,
)
# Save results
base_name = os.path.splitext(image_file)[0]
save_path = os.path.join(image_dir, f"{base_name}_detection_results.png")
visualize_results(image, results, save_path=save_path, show_plot=False)
image.close() # Release pixel buffer between batch iterations.
# Count tables by type
table_counts = {0: 0, 1: 0}
for label in results['labels']:
table_counts[label.item()] += 1
# Store summary
results_summary.append({
'file': image_file,
'tables_found': len(results['scores']),
'information_tables': table_counts[0],
'marks_tables': table_counts[1],
'max_confidence': float(results['scores'].max()) if len(results['scores']) > 0 else 0.0
})
except Exception as e:
print(f"Error processing {image_file}: {e}")
results_summary.append({
'file': image_file,
'tables_found': 0,
'information_tables': 0,
'marks_tables': 0,
'max_confidence': 0.0,
'error': str(e)
})
# Print summary
print("\n" + "="*60)
print("BATCH PROCESSING SUMMARY")
print("="*60)
total_tables = 0
total_info_tables = 0
total_marks_tables = 0
for result in results_summary:
print(f"{result['file']:<30} | Total: {result['tables_found']:<3} | Info: {result['information_tables']:<3} | Marks: {result['marks_tables']:<3} | Max Conf: {result['max_confidence']:.3f}")
total_tables += result['tables_found']
total_info_tables += result['information_tables']
total_marks_tables += result['marks_tables']
print(f"\nTotal tables detected: {total_tables}")
print(f" - Information Tables: {total_info_tables}")
print(f" - Marks Tables: {total_marks_tables}")
print(f"Average tables per image: {total_tables/len(image_files):.2f}")
# Return 1 if any tables found, 0 if none
return 1 if total_tables > 0 else 0
def main():
parser = argparse.ArgumentParser(description="Table Detection Inference")
parser.add_argument("--image", type=str, help="Path to single image")
parser.add_argument("--dir", type=str, help="Path to directory of images")
parser.add_argument("--model", type=str, default="models\tt_finetuned",
help="Path to fine-tuned model (default: models\tt_finetuned)")
parser.add_argument("--confidence", type=float, default=0.5,
help="Confidence threshold (default: 0.5)")
parser.add_argument("--info-thresh", type=float, default=0.5,
help="Per-class threshold for Information Table (class 0) (default: 0.5)")
parser.add_argument("--marks-thresh", type=float, default=0.8,
help="Per-class threshold for Marks Table (class 1) (default: 0.8)")
parser.add_argument("--no-fix-orientation", action="store_true",
help="Disable EXIF orientation fix for images")
parser.add_argument("--no-save", action="store_true",
help="Don't save visualization results")
args = parser.parse_args()
if not args.image and not args.dir:
print("Please provide either --image or --dir argument")
print("Example usage:")
print(" python predict_table.py --image sample.jpg")
print(" python predict_table.py --dir ./test_images/")
return
if args.image:
if not os.path.exists(args.image):
print(f"Image not found: {args.image}")
return
result = process_single_image(
args.image,
args.model,
args.confidence,
info_threshold=args.info_thresh,
marks_threshold=args.marks_thresh,
save_results=not args.no_save,
fix_orientation=not args.no_fix_orientation,
)
exit(result)
elif args.dir:
if not os.path.exists(args.dir):
print(f"Directory not found: {args.dir}")
return
result = process_batch_images(
args.dir, args.model, args.confidence,
info_threshold=args.info_thresh,
marks_threshold=args.marks_thresh,
fix_orientation=not args.no_fix_orientation,
)
exit(result)
if __name__ == "__main__":
main()