pdfmarks
Read the data back out of a vector figure in a PDF.
A scatter plot in a paper looks like a picture of the data, and if you want the numbers the usual answer is to email the authors or trace the dots by hand. But a vector PDF does not contain a picture. It contains a list of instructions for placing marks, and the coordinate in each instruction is where the measurement was. The numbers never left. They are sitting in the file at full precision.
pdfmarks walks a page's drawing instructions and hands you every mark on it.
pip install pdfmarks
from pdfmarks import marks, affine
ms = marks('paper.pdf', 4) # 0-based page index
# a series is a colour and a marker shape. matplotlib draws a circle with 26
# path points, MATLAB with 15; a triangle is 5, a square or diamond is 4.
series = [m for m in ms
if m['fill'] == (0.121569, 0.466667, 0.705882) and m['npts'] == 26]
series.sort(key=lambda m: m['seq']) # drawing order = data order
# calibrate from two marks whose data values you know -- axis tick stubs are
# ideal, they are short two-point strokes on the frame.
fx, fy = affine((225.28, 292.21), (0.0, 0.0),
(410.30, 427.67), (1.0, 1.0))
data = [(fx(m['x']), fy(m['y'])) for m in series]
Each mark is a dict: x, y (centre, PDF user space, points, y upwards),
w, h (bounding box), fill, stroke, npts, op (the paint operator, or
Do for a placed image), seq (position in drawing order), and pts (every
vertex, so a line series comes back whole).
There is a command line too:
pdfmarks paper.pdf 5 --colours # what series are on page 5
pdfmarks paper.pdf 5 --long 40 # paths long enough to be a line series
pdfmarks paper.pdf 5 --path 27 # every vertex of that path
pdfmarks paper.pdf 5 --csv marks.csv --max-size 12
How exact is it
Exact. It reads numbers, not pixels.
The test suite downloads two real papers and checks two things whose answers
exist independently of this code. In one (a MATLAB figure) two panels carry
copies of a third panel's markers; matched in drawing order the recovered
coordinates agree to 0.0012 pt. In the other (matplotlib) three series are
plotted against a parameter running 0 to 1, and calibrated from nothing but the
tick stubs the recovered values land on k/24 — the authors' linspace,
which the reader is never told about — to 3.3 × 10⁻⁵.
What it does not do
It does not find your axes. Every plotting library writes them differently,
and a wrong guess produces numbers that are confidently wrong to four decimal
places, which is worse than no numbers. You supply two reference points and
affine does the rest. Tick stubs are easy to spot: two-point strokes, a few
points long, sitting on the frame.
It also cannot help with a raster figure. If the plot was saved as PNG and pasted in, there are no instructions to read and you want a tracing tool instead.
Three things worth knowing
Series separate by shape as well as colour. Two panels that reuse one
colour still come apart if their markers differ, because npts differs. Run
--colours and check the counts against what the figure should contain; if a
series is short, something is merging.
Drawing order is usually data order, and often time order. Trajectories come out in the order they were measured. It is also how you tell a legend swatch from data: the swatch is the same marker in the same colour, drawn in a separate run.
A filled marker is often drawn twice, once filled and once as a stroked outline at the same centre. De-duplicate by adjacency in the stream, never by proximity in the plane: in a dense trajectory, neighbouring real data points sit closer together than a marker is wide, and a proximity rule quietly eats them.
Why it exists
It came out of wanting the numbers behind one figure — twelve lab earthquakes published as a scatter plot with no data file. Reading them back out took an afternoon and turned three of the paper's qualitative sentences into measurements. It also caught something invisible: the copies of one panel's data laid into the other two had been translated rather than replotted, so they sat 1.4 % too far right. Harmless, and the sort of thing only the instructions know.
MIT licensed. Written by Iris.
Release files for pdfmarks 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 | |
|---|---|---|---|
| pdfmarks-0.1.0.tar.gz | 12.2 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| pdfmarks-0.1.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 21.5 kB
Release files / pdfmarks-0.1.0.tar.gz
| Download URL | pdfmarks-0.1.0.tar.gz |
|---|---|
| Size | 12.2 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
56b5df0c2dec31effdcebc41e37303cccca0d6659ff8c48367400cc73731c0a3
|
|
BLAKE2b-256 checksum How to use checksums |
d5154f07d01d27fef43a47166a038c8b79104173985839dbaf4b95a07c531346
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.10.12
|
Release files / pdfmarks-0.1.0-py3-none-any.whl
| Download URL | pdfmarks-0.1.0-py3-none-any.whl |
|---|---|
| Size | 9.2 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
eb0711a6bce8a61d3f967299b8d52598fe0a4902fe2837bc8bfbd70956e55980
|
|
BLAKE2b-256 checksum How to use checksums |
212ca08f6cb696266192c6c01a95e315e4b7f53161d3af63d4ee87047763406f
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.10.12
|