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_scanssubdirectory 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 betruefor exactly one option per question.
- Each option object has:
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-pathcan be a single PDF file or a folder of images (PNG, JPG). - The
--exam-dirmust contain theexam_model_*_questions.jsonfiles 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
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
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
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
8b1283e3a567501de3f1cd607eb0d6835f0b1165e157636829adca4ad267607b
|
|
| MD5 |
1247df0d84a52a7f88e273546a7fa563
|
|
| BLAKE2b-256 |
d795b9181c6660a026d9f243441a9262dcc626781ecb1e91fefb03fb29a9d3df
|
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
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
1a94444172e1148882454355a6b8495c9925073acf376e0295f54f50c0a32cd7
|
|
| MD5 |
914489f15814b30d201f766c5387a791
|
|
| BLAKE2b-256 |
0cd1c5afd6a3d74e660e03f9c88dd45c9efb5d4ed196bf01c41d840e71ca2dba
|