Skip to content

Python

Below is a Python example showing how to batch-process a folder of images against the Vaxtor Cloud ALPR API, write results (including plate crops) to an Excel file, and add a summary table at the end. Replace your credentials by setting the environment variables.

Example

python
import os
import requests
import threading
import queue
import time
import argparse
from openpyxl import Workbook
from openpyxl.drawing.image import Image as ExcelImage
from openpyxl.utils import get_column_letter
from openpyxl.styles import Font
from openpyxl.worksheet.table import Table, TableStyleInfo
from PIL import Image as PILImage

# --- Configuration ---
TOKEN_URL         = "https://auth.vaxtor.cloud/connect/token"
API_URL           = "https://vaxalpr.vaxtor.cloud/analysis/api/plate"
CLIENT_ID         = os.getenv("VAXTOR_CLIENT_ID")
CLIENT_SECRET     = os.getenv("VAXTOR_CLIENT_SECRET")
SCOPE             = "api"
IMAGE_FOLDER      = "./images"
OUTPUT_XLSX_FILE  = "vaxtor_analysis_results.xlsx"
NUM_THREADS       = 4

# Optional region override
parser = argparse.ArgumentParser(description="Vaxtor Batch Analysis XLS")
parser.add_argument(
    "--region",
    type=str,
    choices=[
      "north-america", "europe", "central-asia",
      "oceania", "apac", "middle-east"
    ],
    help="Force a specific OCR region (optional)"
)
args = parser.parse_args()
OCR_REGION = args.region

# Prepare Excel workbook
wb = Workbook()
ws = wb.active
ws.title = "Analysis Results"
headers = [
    "Image", "Plate Crop", "image_filename", "plate_number",
    "plate_formatted", "plate_state", "plate_country",
    "plate_category", "plate_read_confidence", "vehicle_make",
    "vehicle_model", "vehicle_color", "vehicle_class",
    "time_request_total_sec", "time_server_proc_sec",
    "time_client_parsing_sec", "time_total_sec"
]
ws.append(headers)

# Threading setup
excel_lock = threading.Lock()
image_queue = queue.Queue()

def get_access_token():
    """Obtain OAuth2 token using client credentials."""
    resp = requests.post(
        TOKEN_URL,
        data={
            "grant_type":    "client_credentials",
            "client_id":     CLIENT_ID,
            "client_secret": CLIENT_SECRET,
            "scope":         SCOPE,
        }
    )
    resp.raise_for_status()
    return resp.json().get("access_token")

def resize_image_for_excel(path, max_size=(300, 300)):
    """Create thumbnail for Excel embedding."""
    img = PILImage.open(path)
    img.thumbnail(max_size)
    out = path + "_resized.png"
    img.save(out, format="PNG")
    return out

def process_image_worker(token):
    """Worker thread: send one image, parse results, write to Excel."""
    while True:
        try:
            fname = image_queue.get_nowait()
        except queue.Empty:
            return

        file_path = os.path.join(IMAGE_FOLDER, fname)
        with excel_lock:
            print(f"[{threading.current_thread().name}] {fname}")

        headers = {"Authorization": f"Bearer {token}"}
        if OCR_REGION:
            headers["Vaxtor-Ocr-Region"] = OCR_REGION

        # Send image as raw binary in the request body (octet-stream)
        with open(file_path, "rb") as f:
            headers["Content-Type"] = "application/octet-stream"
            start_req = time.time()
            resp = requests.post(API_URL, headers=headers, data=f)
            req_time = round(time.time() - start_req, 3)

        resp.raise_for_status()

        data = resp.json().get("results", [])
        total_time = round(time.time() - start_req, 3)

        for item in data:
            plate = item.get("plate", {})
            vehicle = item.get("vehicle", {})

            row = [
              "", "", fname,
              plate.get("number", ""),
              plate.get("formatted", ""),
              plate.get("state", ""),
              plate.get("country", ""),
              plate.get("category", ""),
              plate.get("plateReadConfidence", ""),
              vehicle.get("make", ""),
              vehicle.get("model", ""),
              vehicle.get("color", ""),
              vehicle.get("class", ""),
              req_time,
              round(resp.elapsed.total_seconds(), 3),
              round(time.time() - start_req - resp.elapsed.total_seconds(), 3),
              total_time
            ]

            with excel_lock:
                ws.append(row)
                r = ws.max_row

                # Insert resized full image
                thumb = resize_image_for_excel(file_path)
                img_full = ExcelImage(thumb)
                img_full.anchor = f"A{r}"
                ws.add_image(img_full)

                # Insert plate crop
                bbox = plate.get("plateBoundingBox", [])
                if len(bbox) == 4:
                    img_orig = PILImage.open(file_path)
                    crop = img_orig.crop(tuple(bbox))
                    crop_path = f"{file_path}_crop_{r}.png"
                    crop.save(crop_path)
                    img_crop = ExcelImage(crop_path)
                    img_crop.anchor = f"B{r}"
                    ws.add_image(img_crop)

                    # Adjust row height to fit images
                    h = max(getattr(img_full, "height", 0), getattr(img_crop, "height", 0))
                    ws.row_dimensions[r].height = h * 0.75

        image_queue.task_done()

def add_summary(ws):
    """Append summary stats and Excel table styling."""
    last = ws.max_row
    base = last + 2
    ws[f"C{base}"] = "Summary"; ws[f"C{base}"].font = Font(bold=True)
    stats = [
      ("Images processed",      f"=COUNTA(C2:C{last})"),
      ("Average request (s)",   f"=AVERAGE(N2:N{last})"),
      ("Average server (s)",    f"=AVERAGE(O2:O{last})"),
      ("Average parsing (s)",   f"=AVERAGE(P2:P{last})"),
      ("Average total (s)",     f"=AVERAGE(Q2:Q{last})"),
      ("Plates conf < 80",      f'=COUNTIF(I2:I{last}, "<80")'),
    ]
    for i, (label, formula) in enumerate(stats, start=1):
        ws[f"C{base+i}"] = label
        ws[f"D{base+i}"] = formula

    table = Table(displayName="Results", ref=f"C1:Q{last}")
    style = TableStyleInfo(name="TableStyleMedium9", showRowStripes=True)
    table.tableStyleInfo = style
    ws.add_table(table)

def run():
    token = get_access_token()
    if not token:
        print("Failed to get access token."); return

    if not os.path.isdir(IMAGE_FOLDER):
        print("Image folder not found."); return

    for f in os.listdir(IMAGE_FOLDER):
        if f.lower().endswith((".jpg", ".jpeg")):
            image_queue.put(f)

    threads = []
    for _ in range(NUM_THREADS):
        t = threading.Thread(target=process_image_worker, args=(token,))
        t.start()
        threads.append(t)

    image_queue.join()
    for t in threads:
        t.join()

    for col in range(1, len(headers)+1):
        ws.column_dimensions[get_column_letter(col)].width = 20

    add_summary(ws)
    wb.save(OUTPUT_XLSX_FILE)
    print(f"✅ Done! Results: {OUTPUT_XLSX_FILE}")

if __name__ == "__main__":
    run()

Code Overview

  1. Authentication

The script retrieves a bearer token via OAuth2 Client Credentials at https://auth.vaxtor.cloud/connect/token.

Setup: Export your credentials before running:

bash
export VAXTOR_CLIENT_ID="your-client-id"
export VAXTOR_CLIENT_SECRET="your-client-secret"
  1. Image Processing Pipeline
  • Scans the ./images folder for .jpg or .jpeg files.
  • Uses a thread pool (NUM_THREADS) to parallelize API calls for faster throughput.
  • You can optionally force a specific OCR region with the --region flag:
bash
python3 batch_analysis.py --region europe
  1. API Call & Response Parsing
  • Sends each image to the ALPR endpoint at https://vaxalpr.vaxtor.cloud/analysis/api/plate
  • Parses returned JSON for plate (number, formatted, state, country, etc.) and vehicle (make, model, color, class) fields.
  • Measures three timing metrics: Request time (client→server round-trip), Server processing time (returned by response.elapsed), Client parsing time
  1. Excel Report Generation
  • Creates an Excel workbook (openpyxl) with a header row and detailed columns.
  1. Customization & Extensibility
  • Adjust constants at the top (e.g., NUM_THREADS, IMAGE_FOLDER, OUTPUT_XLSX_FILE).
  • Extend headers or summary formulas by modifying the headers list or the add_summary function.
  • Integrate additional API endpoints by copying the request pattern shown.

Vaxtor Technologies