Skip to main content

A Python library for generating and correcting exams using Playwright and OpenCV.

Project description

Pexams: Python exam generation and correction

Pexams is a library for generating beautiful multiple-choice exam sheets from simple data structures and automatically correcting them from scans using computer vision. It is similar to R/exams, but written in Python and using Playwright for high-fidelity PDF generation instead of LaTeX. It has the following advantages:

  • It is much easier to install, as it requires only Python (no R, LaTeX, or any other external dependencies).
  • It is easier to customize (it's Python + HTML/CSS!).
  • It is less prone to compilation errors (again, no LaTeX!).

NOTE: This library is still in development and is not yet ready for production use. Although everything should work, there may be some bugs, missing features, or breaking changes in future versions.

Installation

The library has been tested on Python 3.11.

1. Install the library

You can install the library from PyPI:

pip install pexams

If the previous command failed, you can install the library from GitHub:

pip install git+https://github.com/OscarPellicer/pexams.git

Alternatively, you can clone the repository and install it in editable mode, which is useful for development, testing and pushing changes to the repository:

git clone https://github.com/OscarPellicer/pexams.git
cd pexams
pip install -e .

2. Install Playwright browsers

pexams uses Playwright to convert HTML to PDF. You need to download the necessary browser binaries by running:

playwright install chromium

This command only needs to be run once.

Quick start

This example will guide you through generating an exam with 2 different models, creating 4 simulated student scans, and then correcting them.

After installation, the pexams command is available globally in your environment. For the following example, we assume you are running the commands from the root of the project where sample_test.json is located.

1. Generate the exams

Run the following command:

pexams generate --questions-json sample_test.json --output-dir ./exam_output --num-models 2 --generate-fakes 4 --columns 2 --exam-title "Sample Exam" --exam-course "Everything 101" --exam-date "2025-10-26"

This will create an exam_output directory containing:

  • PDF files for 2 exam models.
  • JSON files for each model containing the question data and correct answers.
  • A simulated_scans subdirectory with 4 sample PNGs of filled answer sheets.

2. Correct the exams

Now, let's correct the simulated scans:

pexams correct --input-path ./exam_output/simulated_scans/ --exam-dir ./exam_output/ --output-dir ./correction_results

This will create a correction_results directory with a CSV report and annotated images of each corrected scan.

Visual examples

You can view an example of a fully generated exam PDF here.

Below is an example of a simulated answer sheet and the annotated, corrected version that the library produces.

Simulated Scan Corrected Scan

The analysis module also generates a plot showing the distribution of answers for each question, which helps in identifying problematic questions, as well as a plot showing the distribution of marks, which helps in assessing the fairness of the exam.

Answer distribution Marks distribution

Usage

Input format (JSON)

The generate command expects a JSON file containing the exam questions. The JSON file must conform to the following schema:

  • The root object should have a single key, questions, which is an array of question objects.
  • Each question object has the following keys:
    • id (integer, required): A unique identifier for the question.
    • text (string, required): The question text. You can use Markdown here. For LaTeX, enclose it in $...$.
    • options (array, required): A list of option objects.
      • Each option object has:
        • text (string, required): The option text.
        • is_correct (boolean, required): Must be true for exactly one option per question.
    • image_source (string, optional): A path to a local image or a URL.

Example questions.json:

{
  "questions": [
    {
      "id": 1,
      "text": "What is the capital of France?",
      "options": [
        { "text": "Berlin", "is_correct": false },
        { "text": "Madrid", "is_correct": false },
        { "text": "Paris", "is_correct": true },
        { "text": "Rome", "is_correct": false }
      ]
    }
  ]
}

Command line

Generating exams

pexams generate --questions-json <path_to_questions.json> --output-dir <results_directory> [OPTIONS]

Common options:

  • --num-models <int>: Number of exam variations to generate (default: 4).
  • --exam-title <str>: Title for the exam (default: "Final Exam").
  • --exam-course <str>: Course name for the exam (optional).
  • --exam-date <str>: Date of the exam (optional).
  • --font-size <str>: Base font size, e.g., '10pt' (default: '11pt').
  • --columns <int>: Number of question columns (1, 2, or 3; default: 1).
  • --generate-fakes <int>: Number of simulated scans to generate for testing.
  • --generate-references: Generates a reference scan with correct answers for each model.

Correcting exams

pexams correct --input-path <path_to_scans> --exam-dir <path_to_exam_models> --output-dir <results_directory>
  • The --input-path can be a single PDF file or a folder of images (PNG, JPG).
  • The --exam-dir must contain the exam_model_*_questions.json files generated alongside the exam PDFs.

Python API

You can also programatically use the library from Python to generate and correct exams.

Generating exams

from pexams import generate_exams
from pexams.schemas import PexamQuestion, PexamOption

# 1. Create your list of questions
questions = [
    PexamQuestion(
        id=1,
        text="What is the capital of France?",
        options=[
            PexamOption(text="Berlin", is_correct=False),
            PexamOption(text="Madrid", is_correct=False),
            PexamOption(text="Paris", is_correct=True),
            PexamOption(text="Rome", is_correct=False),
        ]
    ),
    # ... more questions
]

# 2. Generate the exam PDFs
generate_exams(
    questions=questions,
    output_dir="my_exams",
    num_models=4,
    exam_title="Geography Quiz",
    exam_course="GEO101",
    lang="en"
)

Correcting exams

from pexams import correct_exams

# In a real scenario, you would load the solutions that were 
# generated by the `generate_exams` function.
solutions_per_model = {
    "1": { # model_id
        1: 2,  # Question 1, correct option is index 2 ('C')
        2: 0,
        # ... more solutions for model 1
    },
    "2": {
        1: 0,
        2: 3,
        # ... more solutions for model 2
    }
}

# Correct the scanned PDF or image folder
correct_exams(
    input_path="scans/all_scans.pdf",
    solutions_per_model=solutions_per_model,
    output_dir="results"
)

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

pexams-0.1.0.tar.gz (30.6 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

pexams-0.1.0-py3-none-any.whl (30.4 kB view details)

Uploaded Python 3

File details

Details for the file pexams-0.1.0.tar.gz.

File metadata

  • Download URL: pexams-0.1.0.tar.gz
  • Upload date:
  • Size: 30.6 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.1.0 CPython/3.13.7

File hashes

Hashes for pexams-0.1.0.tar.gz
Algorithm Hash digest
SHA256 8b1283e3a567501de3f1cd607eb0d6835f0b1165e157636829adca4ad267607b
MD5 1247df0d84a52a7f88e273546a7fa563
BLAKE2b-256 d795b9181c6660a026d9f243441a9262dcc626781ecb1e91fefb03fb29a9d3df

See more details on using hashes here.

File details

Details for the file pexams-0.1.0-py3-none-any.whl.

File metadata

  • Download URL: pexams-0.1.0-py3-none-any.whl
  • Upload date:
  • Size: 30.4 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.1.0 CPython/3.13.7

File hashes

Hashes for pexams-0.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 1a94444172e1148882454355a6b8495c9925073acf376e0295f54f50c0a32cd7
MD5 914489f15814b30d201f766c5387a791
BLAKE2b-256 0cd1c5afd6a3d74e660e03f9c88dd45c9efb5d4ed196bf01c41d840e71ca2dba

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page