OMRChecker: grading multiple choice sheets from scans or a phone camera
Evaluate OMR sheets fast and accurately using a scanner 🖨 or your phone 🤳.
At a glance
- What is it?
- OMRChecker is a Python tool that reads optical mark recognition sheets, the bubble sheets used in exams and surveys, and produces the marked responses and their scores. It is aimed at people who have to grade a stack of paper quickly, with a layout you configure once and images from a scanner or a phone camera afterwards.
- Who is it for?
- OMRChecker fits a teacher, a competition organiser or a survey researcher who has a fixed form layout and hundreds of sheets to grade, and who wants the results in a spreadsheet rather than in a vendor's dashboard. It does not fit a one-off form, because configuring a layout is the work, and it does not fit anyone who needs the mobile accuracy to be as good as the scanner accuracy.
- Can I use it commercially?
- Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- Yes. The repository last received commits 51 days ago.
- What is it written in?
- Mainly Python, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on October 25, 2026, and from our analysis. They are not legal advice.
Editorial analysis
What the tool does, in one paragraph
OMR stands for Optical Mark Recognition, used to detect and interpret human-marked data on documents. The README describes the process as reading and evaluating OMR sheets, and says the format is commonly used in exams, surveys and other forms.
The workflow is short. Once you configure the OMR layout, you throw images of the sheets at the software and you get back the marked responses in a spreadsheet. The images can be taken from various angles, and the README shows the same code running on images from a document scanner and on images from a mobile phone, which is the distinction that matters for accuracy.
The project is a Python package in the usual research-tool shape, with a main.py at the root, the code under src/, an inputs/ directory, a samples/ directory with several example sets, a docs/ directory, and a wiki with the user guide and a TODOs page, a contributor guide in the repository, and a project ideas list on a GitHub project board. There is also a Discord community and a Colab notebook for running it in the cloud.
The licence is MIT, the last push was on 2026-09-04, and the two releases are v1.0.0 from November 2020 and v1.1.0 from April 2024.
Accuracy is the number that decides the use case
The features table is a list of claims with numbers attached, and the first one is the one you have to think about.
The accuracy claim is nearly 100 percent on good quality document scans, and about 90 percent on mobile images. That gap is the whole story of the project in one line, and it is the reason the README shows the same code working on scanner output and on phone photos side by side.
For an exam, a 90 percent accuracy is not a rounding error, it is one sheet in ten scored wrongly. Which is why the tool is at its best on a flatbed scanner, and why the mobile figure should be read as a convenience for small batches rather than as a grading path.
The robustness claim is separate and is about the input rather than the decision. The software supports low resolution and xeroxed sheets, with a dedicated Robustness page in the wiki. Photocopying is the normal condition of a stack of answer sheets that has been passed around a department, so this is a realistic requirement rather than a nice extra.
Put together, the claim is that the tool is reliable on good scans and degrades on phone photos. That is an honest framing, and the fact that the numbers are given per input type is what makes them useful.
Throughput, and what the speed figure excludes
The speed claim is 200 OMRs per minute, and the wording matters. The table says that is the current processing speed without any optimization, which tells you two things at once: the number is a floor rather than a ceiling, and the author has not needed to optimise because the unoptimised path is already fast enough for the scale the tool is used at.
The scale claim is that it was tested on a large scale at Technothlon, which is a competition with answer sheets to grade in a fixed window. That is a genuinely demanding test case and a better reference point than a benchmark on synthetic images.
The lightweight claim is about code size rather than runtime, that the core code is very minimal, and it is paired with the developer friendly claim: the code is Pylinted and Black formatted, and there is a community on Discord. The two together are what makes the project usable as a base for something else, because a tool you can read and format consistently is a tool you can extend.
And there is a hint about where the extension points are. The README points developers looking for interesting challenges at a TODOs page in the wiki, which is the shortest possible roadmap.
The layout is the configuration, and the samples are the documentation
Everything in this tool hangs off one artefact: the OMR layout. Once it is configured, the rest is mechanical.
The samples directory shows how much that matters, with a directory for the answer key, one for community contributed material, and six numbered sample sets. Each of those is a complete working configuration, which makes the samples the fastest way to understand the layout format, and the answer-key directory shows where the correct answers live, which is a separate file from the layout.
The customisation claim is that you can easily apply the tool to custom OMR layouts, surveys and so on, with the user guide linked from the features table. The visuals page exists to make that configuration debuggable, and the processing steps image in the README, which is generated by the code itself, shows what happens to a sheet as it is read.
There is also a JSON schema in the dependency list, jsonschema, which is the natural way to validate a layout file and an answer key against the expected shape, and the answer key being a machine-readable file rather than a spreadsheet is what allows a whole batch to be scored in one pass.
The output is a spreadsheet, and the diagnostics are pictures
The output side is deliberately plain. You get a CSV sheet containing the detected responses and the evaluated scores, and there is a coloured output as well, which is a rendering of the same result with the marks highlighted so you can see what the software saw on each bubble.
That combination is the right design for the use case. The CSV is what you load into a spreadsheet and work with, and the coloured sheet is what you look at when a score looks wrong and you need to know whether the student marked the wrong bubble or the software misread a smudged one.
The README is careful about where the visuals live, pointing to the Rich Visuals page in the wiki for the many more of them, and the output image sits in the wiki extras and the coloured output in the docs assets.
For an organiser, that means a grading run produces a file you can act on and a file you can check, which is a small thing that decides whether you trust the result enough to publish it.
The repository, and the tooling around it
The dependency list is short and tells you what the tool does and does not do.
There is numpy and pandas for the data side, matplotlib for the visualisations, rich for the console output, screeninfo for the display information that the layout configuration needs, deepmerge and dotmap for merging and reading the configuration, jsonschema for validating it, and PyMuPDF for PDF handling, which is how a stack of scanned sheets usually arrives.
The image processing library is not in that list, even though the repository topics include OpenCV, so the image work is either in a development requirement or behind the main entry point, and the runtime requirements file is deliberately small.
The project tooling is modern Python practice: Black with a line length of 88 and a target version of py37, a .pylintrc, a pytest.ini, a pyproject.toml carrying the Black configuration, separate requirements.txt and requirements.dev.txt, and a pre-commit configuration. The Python version target being 3.7 is worth noticing, since it is what the formatter is configured for rather than what the code necessarily requires.
The .github/ directory holds the workflows and the issue templates, and the contributor guide, the code of conduct and a Contributors.md file are in the root.
The state of the project, and who it is for
The release history is short and the work continues between releases. v1.0.0 came in November 2020, v1.1.0 in April 2024, and the repository was last pushed to on 2026-09-04.
The gap between the two releases and the continued commits say that this is a project in maintenance with an active community rather than something being actively released, and the badges tell the rest of the story. There is a badge asking whether it is maintained, pointing at the TODOs page, a badge for pull requests being welcome, a badge for closed issues and pull requests, and a discussion badge on a single long running issue.
The documentation is distributed in a way that suits a project with a mixed audience. The wiki carries the user guide, the robustness notes, the rich visuals and the evaluation notes, the contributor guide is in the repository, and the project ideas list lives as a GitHub project board. There is a Colab notebook for running the tool without installing anything, which is the sensible first step for someone who only wants to try it on a photograph of one sheet.
And the scope of the audience is set by the original problem. This is a tool for a fixed form, a stack of paper and a deadline, which is a narrower and more tractable problem than general purpose document understanding, and it is the reason the accuracy numbers can be stated per input type rather than hedged.
Editorial conclusion
OMRChecker fits a teacher, a competition organiser or a survey researcher who has a fixed form layout and hundreds of sheets to grade, and who wants the results in a spreadsheet rather than in a vendor's dashboard. It does not fit a one-off form, because configuring a layout is the work, and it does not fit anyone who needs the mobile accuracy to be as good as the scanner accuracy. Verify three things before you rely on it: that your form layout can be described in the project's format, since the samples directory shows what a template looks like, that you accept the accuracy difference between a flatbed scan and a phone photo, which the README puts at nearly 100 percent against about 90 percent, and that your answer key is in the expected form, with a dedicated answer-key sample and a JSON schema in the dependencies to validate it.
Frequently asked questions
What does OMRChecker do and how do I use it?
It reads optical mark recognition sheets, the bubble sheets used in exams and surveys. You configure the OMR layout once, then feed it images of the sheets from a scanner or a phone camera, and you get back the marked responses and the evaluated scores in a CSV sheet.
How accurate is OMRChecker?
The features table states nearly 100 percent accuracy on good quality document scans, and about 90 percent on mobile images. The tool also supports low resolution and xeroxed sheets, and there is a dedicated Robustness page in the wiki for the details.
How fast does OMRChecker process sheets?
The stated processing speed is 200 OMRs per minute, and the table is explicit that this is without any optimization. The tool was also tested at large scale at Technothlon, which is the practical reference for how much paper this can deal with in a fixed window.
Can OMRChecker handle my own form layout?
Yes. The features list says you can easily apply it to custom OMR layouts and surveys, with the user guide in the wiki explaining how, and the samples/ directory holds six example sets plus an answer-key directory and a community directory, which together show the layout format and where the correct answers go.
What does OMRChecker need to run?
The runtime requirements are deepmerge, dotmap, jsonschema, matplotlib, numpy, pandas, rich, screeninfo and PyMuPDF. The code is Black formatted and Pylinted, tests run under pytest, and there is a pre-commit configuration and a Discord community for questions.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/udayraj123-omrchecker)Was this useful?