unmark
Remove pen/pencil markings from a PDF - fully offline. No AI, no network. Your files never leave your machine.
pip install unmark
unmark erase "marked.pdf" # -> "marked clean.pdf"
erase - the main feature
Strips the markings and hands back a clean PDF. It picks the right path automatically:
- Digital annotations (marks added in GoodNotes / Preview / Adobe, etc.) → deleted losslessly; the original page is restored exactly, text stays text.
- Marks baked into a scan → colour ink (red/blue/green pen) is removed by
channel-dominance detection that leaves the printed text untouched. Options:
--pencil(light-grey graphite),--lines(experimental underline erase).
unmark erase "scan.pdf" out.pdf --pencil # also remove light pencil
unmark erase "scan.pdf" --sat 10 # remove more colour (may leave less)
unmark erase "notes.pdf" --lossless # PNG pages instead of JPEG
What erase can and can't do (honest)
| mark | result |
|---|---|
| digital annotation | ✅ removed perfectly (lossless) |
| red / blue / green pen | ✅ removed; text preserved |
| light-grey pencil | ⚙️ removed with --pencil |
| dark pen / heavy pencil at the same tone as the print | ❌ cannot be removed offline - it carries no colour or brightness difference from the real letters. Only AI or manual erasing gets those. |
reflow - experimental
⚠️ Experimental.
reflowOCRs the printed text (offline, via RapidOCR) and re-typesets a fresh PDF. On clean, single-column papers it's decent. On dense, two-column, or heavily marked papers it is unreliable - it misreads circled option markers, over-splits questions, and leftover dark handwriting bleeds into the text. Always proofread. Offline OCR simply can't match a vision model on messy scans; that's a hard ceiling, not a bug.
pip install "unmark[ocr]" # adds the offline OCR engine
unmark reflow "scan.pdf" --columns 1 # single-column works best
unmark reflow "scan.pdf" --pages 1-4 # limit to some pages
How it works
erase: PyMuPDF renders each page → numpy colour/pencil masking → repackaged as a
compact greyscale PDF (or the annotation layer is simply deleted).
reflow: colour-clean → split columns → RapidOCR → parse → re-typeset (Story API).
License
MIT.
Metadata
Release files for unmark 0.1.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| unmark-0.1.0.tar.gz | 11.6 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| unmark-0.1.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 23.8 kB
Release files / unmark-0.1.0.tar.gz
| Download URL | unmark-0.1.0.tar.gz |
|---|---|
| Size | 11.6 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
d007c8e511bd23a1f132662a99d5ed1a92c1b5c79e77897c5bc9e3b36b995c03
|
|
BLAKE2b-256 checksum How to use checksums |
e4dcf37b022aa8ece0524ebbdf8e0cc612a7edce76c8868f7e26023de9939960
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.2.0 CPython/3.14.5
|
Release files / unmark-0.1.0-py3-none-any.whl
| Download URL | unmark-0.1.0-py3-none-any.whl |
|---|---|
| Size | 12.2 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
33bbbbdab49517ddf068b3613e7c75e853f32a109f445896f5076b74136c7226
|
|
BLAKE2b-256 checksum How to use checksums |
fa44186f10efa9aafaf852dcaaed35b0062b706a65d3c6b0f7f237444ea81bc2
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.2.0 CPython/3.14.5
|