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