trace-digitiser 2.0.0__tar.gz → 2.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {trace_digitiser-2.0.0 → trace_digitiser-2.1.0}/CHANGELOG.md +61 -0
- {trace_digitiser-2.0.0/src/trace_digitiser.egg-info → trace_digitiser-2.1.0}/PKG-INFO +60 -13
- {trace_digitiser-2.0.0 → trace_digitiser-2.1.0}/README.md +57 -10
- {trace_digitiser-2.0.0 → trace_digitiser-2.1.0}/RELEASING.md +10 -9
- {trace_digitiser-2.0.0 → trace_digitiser-2.1.0}/pyproject.toml +6 -2
- trace_digitiser-2.1.0/src/trace_digitiser/__init__.py +361 -0
- trace_digitiser-2.1.0/src/trace_digitiser/_figure.py +480 -0
- trace_digitiser-2.1.0/src/trace_digitiser/_results.py +240 -0
- {trace_digitiser-2.0.0 → trace_digitiser-2.1.0}/src/trace_digitiser/axis_labels.py +94 -26
- trace_digitiser-2.1.0/src/trace_digitiser/bars.py +324 -0
- {trace_digitiser-2.0.0 → trace_digitiser-2.1.0}/src/trace_digitiser/calibration.py +9 -3
- {trace_digitiser-2.0.0 → trace_digitiser-2.1.0}/src/trace_digitiser/cli.py +246 -0
- trace_digitiser-2.1.0/src/trace_digitiser/explore.py +193 -0
- {trace_digitiser-2.0.0 → trace_digitiser-2.1.0}/src/trace_digitiser/frames.py +13 -4
- trace_digitiser-2.1.0/src/trace_digitiser/heatmap.py +368 -0
- trace_digitiser-2.1.0/src/trace_digitiser/markers.py +399 -0
- {trace_digitiser-2.0.0 → trace_digitiser-2.1.0}/src/trace_digitiser/ocr.py +22 -2
- {trace_digitiser-2.0.0 → trace_digitiser-2.1.0}/src/trace_digitiser/panel_detection.py +50 -0
- trace_digitiser-2.1.0/src/trace_digitiser/points.py +194 -0
- trace_digitiser-2.1.0/src/trace_digitiser/polar.py +320 -0
- trace_digitiser-2.1.0/src/trace_digitiser/rows.py +313 -0
- {trace_digitiser-2.0.0 → trace_digitiser-2.1.0}/src/trace_digitiser/trace_detection.py +11 -4
- {trace_digitiser-2.0.0 → trace_digitiser-2.1.0}/src/trace_digitiser/x_calibration.py +1 -2
- {trace_digitiser-2.0.0 → trace_digitiser-2.1.0/src/trace_digitiser.egg-info}/PKG-INFO +60 -13
- {trace_digitiser-2.0.0 → trace_digitiser-2.1.0}/src/trace_digitiser.egg-info/SOURCES.txt +10 -0
- trace_digitiser-2.1.0/tests/test_readers.py +417 -0
- {trace_digitiser-2.0.0 → trace_digitiser-2.1.0}/tests/test_trace_detection.py +4 -1
- trace_digitiser-2.0.0/src/trace_digitiser/__init__.py +0 -625
- {trace_digitiser-2.0.0 → trace_digitiser-2.1.0}/LICENSE +0 -0
- {trace_digitiser-2.0.0 → trace_digitiser-2.1.0}/MANIFEST.in +0 -0
- {trace_digitiser-2.0.0 → trace_digitiser-2.1.0}/examples/README.md +0 -0
- {trace_digitiser-2.0.0 → trace_digitiser-2.1.0}/examples/digitise_published_figure.py +0 -0
- {trace_digitiser-2.0.0 → trace_digitiser-2.1.0}/examples/lopatina2014_fig4.jpg +0 -0
- {trace_digitiser-2.0.0 → trace_digitiser-2.1.0}/examples/validate_with_known_data.py +0 -0
- {trace_digitiser-2.0.0 → trace_digitiser-2.1.0}/examples/walkthrough.ipynb +0 -0
- {trace_digitiser-2.0.0 → trace_digitiser-2.1.0}/setup.cfg +0 -0
- {trace_digitiser-2.0.0 → trace_digitiser-2.1.0}/src/trace_digitiser/diagnostics.py +0 -0
- {trace_digitiser-2.0.0 → trace_digitiser-2.1.0}/src/trace_digitiser/digitise.py +0 -0
- {trace_digitiser-2.0.0 → trace_digitiser-2.1.0}/src/trace_digitiser/geometry.py +0 -0
- {trace_digitiser-2.0.0 → trace_digitiser-2.1.0}/src/trace_digitiser/io.py +0 -0
- {trace_digitiser-2.0.0 → trace_digitiser-2.1.0}/src/trace_digitiser/line_detection.py +0 -0
- {trace_digitiser-2.0.0 → trace_digitiser-2.1.0}/src/trace_digitiser/models.py +0 -0
- {trace_digitiser-2.0.0 → trace_digitiser-2.1.0}/src/trace_digitiser/provenance.py +0 -0
- {trace_digitiser-2.0.0 → trace_digitiser-2.1.0}/src/trace_digitiser/resampling.py +0 -0
- {trace_digitiser-2.0.0 → trace_digitiser-2.1.0}/src/trace_digitiser/straighten.py +0 -0
- {trace_digitiser-2.0.0 → trace_digitiser-2.1.0}/src/trace_digitiser/summarise.py +0 -0
- {trace_digitiser-2.0.0 → trace_digitiser-2.1.0}/src/trace_digitiser/synthetic.py +0 -0
- {trace_digitiser-2.0.0 → trace_digitiser-2.1.0}/src/trace_digitiser/validation.py +0 -0
- {trace_digitiser-2.0.0 → trace_digitiser-2.1.0}/src/trace_digitiser.egg-info/dependency_links.txt +0 -0
- {trace_digitiser-2.0.0 → trace_digitiser-2.1.0}/src/trace_digitiser.egg-info/entry_points.txt +0 -0
- {trace_digitiser-2.0.0 → trace_digitiser-2.1.0}/src/trace_digitiser.egg-info/requires.txt +0 -0
- {trace_digitiser-2.0.0 → trace_digitiser-2.1.0}/src/trace_digitiser.egg-info/top_level.txt +0 -0
- {trace_digitiser-2.0.0 → trace_digitiser-2.1.0}/tests/__init__.py +0 -0
- {trace_digitiser-2.0.0 → trace_digitiser-2.1.0}/tests/conftest.py +0 -0
- {trace_digitiser-2.0.0 → trace_digitiser-2.1.0}/tests/data/lopatina2014_fig4_reference.csv +0 -0
- {trace_digitiser-2.0.0 → trace_digitiser-2.1.0}/tests/test_axis_labels.py +0 -0
- {trace_digitiser-2.0.0 → trace_digitiser-2.1.0}/tests/test_calibration.py +0 -0
- {trace_digitiser-2.0.0 → trace_digitiser-2.1.0}/tests/test_cli.py +0 -0
- {trace_digitiser-2.0.0 → trace_digitiser-2.1.0}/tests/test_digitise.py +0 -0
- {trace_digitiser-2.0.0 → trace_digitiser-2.1.0}/tests/test_frames.py +0 -0
- {trace_digitiser-2.0.0 → trace_digitiser-2.1.0}/tests/test_geometry.py +0 -0
- {trace_digitiser-2.0.0 → trace_digitiser-2.1.0}/tests/test_integration.py +0 -0
- {trace_digitiser-2.0.0 → trace_digitiser-2.1.0}/tests/test_io.py +0 -0
- {trace_digitiser-2.0.0 → trace_digitiser-2.1.0}/tests/test_panel_detection.py +0 -0
- {trace_digitiser-2.0.0 → trace_digitiser-2.1.0}/tests/test_published_figure.py +0 -0
- {trace_digitiser-2.0.0 → trace_digitiser-2.1.0}/tests/test_straighten.py +0 -0
- {trace_digitiser-2.0.0 → trace_digitiser-2.1.0}/tests/test_validation.py +0 -0
|
@@ -4,6 +4,66 @@ All notable changes to trace-digitiser are recorded here.
|
|
|
4
4
|
The format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/)
|
|
5
5
|
and the project uses [semantic versioning](https://semver.org/).
|
|
6
6
|
|
|
7
|
+
## [2.1.0] - 2026-11-14
|
|
8
|
+
|
|
9
|
+
Everything in 2.0.0 works as before. What is new is readers for other kinds of
|
|
10
|
+
figure, and helpers for graphical tools such as the
|
|
11
|
+
[digitiser workbench](https://github.com/comparativechrono/digitiser-workbench).
|
|
12
|
+
|
|
13
|
+
### Added
|
|
14
|
+
|
|
15
|
+
- **Points**: `digitise_points` (`trace-digitiser points`) reads the markers of
|
|
16
|
+
a scatter or dot plot, or of a line drawn with markers, whatever their
|
|
17
|
+
shape, separating markers that overlap; and the error bars on them,
|
|
18
|
+
vertical or horizontal, to the middle of their caps.
|
|
19
|
+
- **Bars**: `digitise_bars` (`trace-digitiser bars`) reads where each bar
|
|
20
|
+
ends, upright or lying down, rising or hanging from the baseline, and its
|
|
21
|
+
error bar; a legend's patch is left out.
|
|
22
|
+
- **Rows**: `digitise_rows` (`trace-digitiser rows`) reads figures drawn as a
|
|
23
|
+
stack of rows - actograms, double-plotted records, birthograms - bin by bin,
|
|
24
|
+
and the dots that mark a peak in them.
|
|
25
|
+
- **Heat maps**: `digitise_heatmap` (`trace-digitiser heatmap`) reads each
|
|
26
|
+
cell's value from its colour on the colour bar, whose labels are read like
|
|
27
|
+
an axis's (or from a matplotlib colour map).
|
|
28
|
+
- **Polar plots**: `digitise_polar` (`trace-digitiser polar`) finds the polar
|
|
29
|
+
axes and their rings, and reads each point's angle and distance.
|
|
30
|
+
- All of them return a `FigureResult` and write a CSV file, a provenance file
|
|
31
|
+
and, if asked, an overlay, as `digitise` does.
|
|
32
|
+
- `describe_figure` (`trace-digitiser describe`): what the automatic steps
|
|
33
|
+
find in a figure - plot areas, each axis's tick labels and readings, the
|
|
34
|
+
colours drawn, a colour bar, polar axes - as plain values.
|
|
35
|
+
- `pixels_to_values`: points picked by hand, in the figure's units.
|
|
36
|
+
- Tick labels are read on the right-hand side of a plot too (a colour bar, a
|
|
37
|
+
second y axis): `calibrate_axis(..., "y2")`.
|
|
38
|
+
- `y_axis="right"` (`--y-axis right`) reads a line's values against a second
|
|
39
|
+
y axis on the right of the plot (a twin axis); with `trace_colours`, the
|
|
40
|
+
lines drawn against each axis are digitised in a run of their own.
|
|
41
|
+
- `describe_figure` gives the tick marks found on each plot's axes, for a
|
|
42
|
+
program that lets you click on a tick.
|
|
43
|
+
- Colours can be given by name (`"black"`, `"steelblue"`, `"tab:blue"`).
|
|
44
|
+
- Plots stacked on a shared x axis, of which only the lowest has labels, take
|
|
45
|
+
its x scale (with a message saying so).
|
|
46
|
+
- A plot area hidden among others in a grid (a heat map whose dark colours
|
|
47
|
+
hide its axis lines) is found by its frame.
|
|
48
|
+
|
|
49
|
+
### Changed
|
|
50
|
+
|
|
51
|
+
- An axis scale is held to medium confidence when the tick labels support a
|
|
52
|
+
different scale just as well. Tesseract compiled to WebAssembly (as it runs
|
|
53
|
+
in a browser) read the superscript minus signs of a log axis's exponents
|
|
54
|
+
differently from the Tesseract program, and two scales then fitted equally;
|
|
55
|
+
between log scales that tie, the one that steps by a decade is chosen.
|
|
56
|
+
- The sides of filled areas (bars) no longer make a plot area of their own
|
|
57
|
+
inside the real one, which bar charts with a legend could cause.
|
|
58
|
+
- A long axis (ten or more labels) whose labels nearly all agree is not read
|
|
59
|
+
again in the slower ways; fitting the labels of a long axis (the 59 years of
|
|
60
|
+
a birthogram) takes a second or two rather than half a minute, with the same
|
|
61
|
+
result.
|
|
62
|
+
- A heat map's colours are looked up on the colour bar once each, rather than
|
|
63
|
+
once for each pixel: Figure 4 of Hearn and Whitmore (2026) takes 4 seconds
|
|
64
|
+
rather than 45, with the same values.
|
|
65
|
+
- pytesseract is imported only when it is first used.
|
|
66
|
+
|
|
7
67
|
## [2.0.0] - 2026-11-14
|
|
8
68
|
|
|
9
69
|
Every call and command-line option of 1.2.0 still works, with the same
|
|
@@ -105,5 +165,6 @@ First version tracked in this repository; identical to the 1.2.0 files on PyPI.
|
|
|
105
165
|
Releases 0.1.0 to 1.0.0 (6-7 June 2026) were uploaded to PyPI directly and are
|
|
106
166
|
not recorded here.
|
|
107
167
|
|
|
168
|
+
[2.1.0]: https://github.com/comparativechrono/trace-digitiser/compare/v2.0.0...v2.1.0
|
|
108
169
|
[2.0.0]: https://github.com/comparativechrono/trace-digitiser/compare/1d34f75...v2.0.0
|
|
109
170
|
[1.2.0]: https://pypi.org/project/trace-digitiser/1.2.0/
|
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: trace-digitiser
|
|
3
|
-
Version: 2.
|
|
4
|
-
Summary: Recover the data from
|
|
3
|
+
Version: 2.1.0
|
|
4
|
+
Summary: Recover the data from figure images - lines, points, bars, actograms, heat maps - on your own computer: no uploads, no AI model.
|
|
5
5
|
Author: Trace Digitiser Contributors
|
|
6
6
|
License-Expression: MIT
|
|
7
7
|
Project-URL: Homepage, https://github.com/comparativechrono/trace-digitiser
|
|
8
8
|
Project-URL: Repository, https://github.com/comparativechrono/trace-digitiser
|
|
9
9
|
Project-URL: Issues, https://github.com/comparativechrono/trace-digitiser/issues
|
|
10
10
|
Project-URL: Changelog, https://github.com/comparativechrono/trace-digitiser/blob/main/CHANGELOG.md
|
|
11
|
-
Keywords: plot digitizer,graph digitiser,data extraction,data rescue,computer vision,time series,circadian,actigraphy
|
|
11
|
+
Keywords: plot digitizer,graph digitiser,data extraction,data rescue,computer vision,time series,circadian,actigraphy,actogram,heat map,scatter plot,bar chart
|
|
12
12
|
Classifier: Development Status :: 4 - Beta
|
|
13
13
|
Classifier: Intended Audience :: Science/Research
|
|
14
14
|
Classifier: Operating System :: OS Independent
|
|
@@ -40,11 +40,12 @@ Dynamic: license-file
|
|
|
40
40
|
|
|
41
41
|
# trace-digitiser
|
|
42
42
|
|
|
43
|
-
Get the numbers back out of a
|
|
43
|
+
Get the numbers back out of a figure when the figure is all that survives — on your own computer.
|
|
44
44
|
|
|
45
45
|
`trace-digitiser` takes an image of a figure (a scan of an old paper, a figure saved from a PDF),
|
|
46
|
-
finds the plot area, follows each trace, and writes the values to a CSV file. It
|
|
47
|
-
|
|
46
|
+
finds the plot area, follows each trace, and writes the values to a CSV file. It reads line plots,
|
|
47
|
+
and also [points, bars, actograms, heat maps and polar plots](#other-kinds-of-figure). It is
|
|
48
|
+
ordinary image processing, written to be read and checked.
|
|
48
49
|
|
|
49
50
|
**Nothing is uploaded and no AI model is involved.** The only other program it runs is
|
|
50
51
|
[Tesseract](https://github.com/tesseract-ocr/tesseract), also on your machine and optional, to read
|
|
@@ -111,6 +112,41 @@ A scale read from labels always comes with a statement of how far to trust it an
|
|
|
111
112
|
left uncalibrated (values from 0 to 1) and you are told so: the aim is never to pass off a wrong
|
|
112
113
|
scale as a good one ([how well that is met](#how-accurate-is-it)).
|
|
113
114
|
|
|
115
|
+
## Other kinds of figure
|
|
116
|
+
|
|
117
|
+
Points, bars, stacked rows, heat maps and polar plots each have a reader of their own. They find
|
|
118
|
+
the plot areas and read the axes exactly as `digitise` does, take the same settings for them
|
|
119
|
+
(`y_min`, `y_ref`, `plot_area`, `exclude` …), and give a table with a row for each thing found:
|
|
120
|
+
|
|
121
|
+
| Figure | Python | Command line | One row for each |
|
|
122
|
+
|---|---|---|---|
|
|
123
|
+
| Scatter or dot plot, line with markers | `digitise_points` | `trace-digitiser points` | point, with its error bars |
|
|
124
|
+
| Bar chart | `digitise_bars` | `trace-digitiser bars` | bar: where it ends, and its error bar |
|
|
125
|
+
| Actogram, double-plotted record, any stack of rows | `digitise_rows` | `trace-digitiser rows` | bin (or column) of each row |
|
|
126
|
+
| Heat map with a colour bar | `digitise_heatmap` | `trace-digitiser heatmap` | cell, from its colour on the bar |
|
|
127
|
+
| Polar plot | `digitise_polar` | `trace-digitiser polar` | point: its angle and distance |
|
|
128
|
+
|
|
129
|
+
```python
|
|
130
|
+
from trace_digitiser import digitise_points, digitise_bars, digitise_rows, digitise_heatmap
|
|
131
|
+
|
|
132
|
+
digitise_points("forest.png", save_overlay=True) # markers, joined by a line or not; error bars too
|
|
133
|
+
digitise_bars("bars.png", y_min=-25, y_max=0) # the legend's patch is left out
|
|
134
|
+
digitise_rows("birthogram.png", row_values=range(1955, 2014), row_spacing_value=1.1,
|
|
135
|
+
bins=24, dots="black") # each row's fill, month by month, and its dot
|
|
136
|
+
digitise_heatmap("ccf.png", rows=21) # the colour bar's labels are read too
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
Each returns a `FigureResult`: `result.data` (the table), `result.warnings`, `result.overlay()`
|
|
140
|
+
and the files written (`<image>_points.csv`, `<image>_points_provenance.json` …).
|
|
141
|
+
|
|
142
|
+
For a program with a graphical interface — the
|
|
143
|
+
[digitiser workbench](https://github.com/comparativechrono/digitiser-workbench), which runs all of
|
|
144
|
+
this in a web browser, where you can mark plot areas, ticks and colours with the mouse —
|
|
145
|
+
`describe_figure` reports what the automatic steps find (plot areas, the tick marks on each axis,
|
|
146
|
+
each axis's tick labels and what was read from them, the colours drawn, a colour bar, polar axes)
|
|
147
|
+
so that they can be shown and corrected, and `pixels_to_values` turns points picked by hand into
|
|
148
|
+
the figure's units. `trace-digitiser describe figure.png` prints the same as JSON.
|
|
149
|
+
|
|
114
150
|
## Check the result
|
|
115
151
|
|
|
116
152
|
1. **Look at the overlay** (`--overlay`). It shows the plot area that was found, the centre and
|
|
@@ -180,6 +216,7 @@ the top-left corner of the image, starting at 0; any image viewer shows them.
|
|
|
180
216
|
| The wrong number of panels | `--layout`, `--rows`, `--cols`, `--panels` | `layout_mode`, `expected_rows`, … |
|
|
181
217
|
| The axis continues beyond its last tick, or has no line at the top or right | `--y-ref ROW:VALUE` twice or more (also `--x-ref`) | `y_ref=[(row, value), …]` |
|
|
182
218
|
| A logarithmic axis, with limits given | `--y-scale log` | `y_scale="log"` |
|
|
219
|
+
| A line drawn against a second y axis, on the right | `--y-axis right` (with `--trace-colour`) | `y_axis="right"` |
|
|
183
220
|
| A trace is missed, or two are merged | `--trace-colour RRGGBB` (repeat per trace) | `trace_colours=["#578dd2", …]` |
|
|
184
221
|
| A legend or annotation is taken for a trace | `--exclude LEFT,TOP,RIGHT,BOTTOM` | `exclude=(l, t, r, b)` |
|
|
185
222
|
| A black trace is invented or ignored | `--achromatic never` / `always` | `achromatic="never"` |
|
|
@@ -253,14 +290,19 @@ does this for a week of minute-by-minute activity drawn at the size of a small j
|
|
|
253
290
|
that is askew is straightened automatically if the tilt is under five degrees; beyond that,
|
|
254
291
|
or for a photograph taken at an angle (where the axes are no longer parallel), correct the
|
|
255
292
|
image first.
|
|
256
|
-
* One trace per colour per panel. Two traces of the same colour
|
|
257
|
-
|
|
293
|
+
* One trace per colour per panel. Two traces of the same colour and dashed or dotted lines are not
|
|
294
|
+
separated or joined up. Markers are read by `digitise_points`.
|
|
258
295
|
* Where traces cross or overlap, the one drawn underneath is hidden and its values there are
|
|
259
|
-
interpolated (`interpolated` is `True`).
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
296
|
+
interpolated (`interpolated` is `True`). Markers that overlap heavily (the inner turns of a
|
|
297
|
+
spiral on a polar plot, say) are separated by their size, and some may be missed or doubled:
|
|
298
|
+
check the overlay.
|
|
299
|
+
* Axis scales are read only from numeric labels on linear or logarithmic axes (on the left, below,
|
|
300
|
+
or for a colour bar or a second y axis, on the right); dates, month names and broken axes are
|
|
301
|
+
not understood: give `x_ref` / `y_ref` for those. Limits typed in are assumed to be at the axis
|
|
302
|
+
lines unless `y_ref` / `x_ref` say otherwise.
|
|
303
|
+
* Shaded bands are not digitised. A fill drawn with an outline of its own colour (matplotlib's
|
|
304
|
+
`fill_between`) is measured to the middle of the outline; bars are measured to their edges, to
|
|
305
|
+
the nearest pixel where the bars' edges are not antialiased.
|
|
264
306
|
|
|
265
307
|
## Development
|
|
266
308
|
|
|
@@ -284,6 +326,11 @@ ruff check src tests
|
|
|
284
326
|
| `ocr.py`, `summarise.py` | Interval labels along the x axis, and means per interval. |
|
|
285
327
|
| `diagnostics.py`, `provenance.py`, `io.py` | Overlay, record of the run, files. |
|
|
286
328
|
| `validation.py`, `synthetic.py` | Figures drawn from known data, and the comparisons. |
|
|
329
|
+
| `_figure.py` | What every reader does first: the image, straightening, plot areas and axes. |
|
|
330
|
+
| `markers.py`, `points.py` | Markers and their error bars. |
|
|
331
|
+
| `bars.py`, `rows.py`, `heatmap.py`, `polar.py` | Bar charts, stacks of rows, heat maps and polar plots. |
|
|
332
|
+
| `explore.py` | `describe_figure` and `pixels_to_values`, for graphical tools. |
|
|
333
|
+
| `_results.py` | What the readers of points, bars, rows, heat maps and polar plots give back. |
|
|
287
334
|
| `cli.py` | The command line. |
|
|
288
335
|
|
|
289
336
|
Changes are listed in [CHANGELOG.md](https://github.com/comparativechrono/trace-digitiser/blob/main/CHANGELOG.md);
|
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
# trace-digitiser
|
|
2
2
|
|
|
3
|
-
Get the numbers back out of a
|
|
3
|
+
Get the numbers back out of a figure when the figure is all that survives — on your own computer.
|
|
4
4
|
|
|
5
5
|
`trace-digitiser` takes an image of a figure (a scan of an old paper, a figure saved from a PDF),
|
|
6
|
-
finds the plot area, follows each trace, and writes the values to a CSV file. It
|
|
7
|
-
|
|
6
|
+
finds the plot area, follows each trace, and writes the values to a CSV file. It reads line plots,
|
|
7
|
+
and also [points, bars, actograms, heat maps and polar plots](#other-kinds-of-figure). It is
|
|
8
|
+
ordinary image processing, written to be read and checked.
|
|
8
9
|
|
|
9
10
|
**Nothing is uploaded and no AI model is involved.** The only other program it runs is
|
|
10
11
|
[Tesseract](https://github.com/tesseract-ocr/tesseract), also on your machine and optional, to read
|
|
@@ -71,6 +72,41 @@ A scale read from labels always comes with a statement of how far to trust it an
|
|
|
71
72
|
left uncalibrated (values from 0 to 1) and you are told so: the aim is never to pass off a wrong
|
|
72
73
|
scale as a good one ([how well that is met](#how-accurate-is-it)).
|
|
73
74
|
|
|
75
|
+
## Other kinds of figure
|
|
76
|
+
|
|
77
|
+
Points, bars, stacked rows, heat maps and polar plots each have a reader of their own. They find
|
|
78
|
+
the plot areas and read the axes exactly as `digitise` does, take the same settings for them
|
|
79
|
+
(`y_min`, `y_ref`, `plot_area`, `exclude` …), and give a table with a row for each thing found:
|
|
80
|
+
|
|
81
|
+
| Figure | Python | Command line | One row for each |
|
|
82
|
+
|---|---|---|---|
|
|
83
|
+
| Scatter or dot plot, line with markers | `digitise_points` | `trace-digitiser points` | point, with its error bars |
|
|
84
|
+
| Bar chart | `digitise_bars` | `trace-digitiser bars` | bar: where it ends, and its error bar |
|
|
85
|
+
| Actogram, double-plotted record, any stack of rows | `digitise_rows` | `trace-digitiser rows` | bin (or column) of each row |
|
|
86
|
+
| Heat map with a colour bar | `digitise_heatmap` | `trace-digitiser heatmap` | cell, from its colour on the bar |
|
|
87
|
+
| Polar plot | `digitise_polar` | `trace-digitiser polar` | point: its angle and distance |
|
|
88
|
+
|
|
89
|
+
```python
|
|
90
|
+
from trace_digitiser import digitise_points, digitise_bars, digitise_rows, digitise_heatmap
|
|
91
|
+
|
|
92
|
+
digitise_points("forest.png", save_overlay=True) # markers, joined by a line or not; error bars too
|
|
93
|
+
digitise_bars("bars.png", y_min=-25, y_max=0) # the legend's patch is left out
|
|
94
|
+
digitise_rows("birthogram.png", row_values=range(1955, 2014), row_spacing_value=1.1,
|
|
95
|
+
bins=24, dots="black") # each row's fill, month by month, and its dot
|
|
96
|
+
digitise_heatmap("ccf.png", rows=21) # the colour bar's labels are read too
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
Each returns a `FigureResult`: `result.data` (the table), `result.warnings`, `result.overlay()`
|
|
100
|
+
and the files written (`<image>_points.csv`, `<image>_points_provenance.json` …).
|
|
101
|
+
|
|
102
|
+
For a program with a graphical interface — the
|
|
103
|
+
[digitiser workbench](https://github.com/comparativechrono/digitiser-workbench), which runs all of
|
|
104
|
+
this in a web browser, where you can mark plot areas, ticks and colours with the mouse —
|
|
105
|
+
`describe_figure` reports what the automatic steps find (plot areas, the tick marks on each axis,
|
|
106
|
+
each axis's tick labels and what was read from them, the colours drawn, a colour bar, polar axes)
|
|
107
|
+
so that they can be shown and corrected, and `pixels_to_values` turns points picked by hand into
|
|
108
|
+
the figure's units. `trace-digitiser describe figure.png` prints the same as JSON.
|
|
109
|
+
|
|
74
110
|
## Check the result
|
|
75
111
|
|
|
76
112
|
1. **Look at the overlay** (`--overlay`). It shows the plot area that was found, the centre and
|
|
@@ -140,6 +176,7 @@ the top-left corner of the image, starting at 0; any image viewer shows them.
|
|
|
140
176
|
| The wrong number of panels | `--layout`, `--rows`, `--cols`, `--panels` | `layout_mode`, `expected_rows`, … |
|
|
141
177
|
| The axis continues beyond its last tick, or has no line at the top or right | `--y-ref ROW:VALUE` twice or more (also `--x-ref`) | `y_ref=[(row, value), …]` |
|
|
142
178
|
| A logarithmic axis, with limits given | `--y-scale log` | `y_scale="log"` |
|
|
179
|
+
| A line drawn against a second y axis, on the right | `--y-axis right` (with `--trace-colour`) | `y_axis="right"` |
|
|
143
180
|
| A trace is missed, or two are merged | `--trace-colour RRGGBB` (repeat per trace) | `trace_colours=["#578dd2", …]` |
|
|
144
181
|
| A legend or annotation is taken for a trace | `--exclude LEFT,TOP,RIGHT,BOTTOM` | `exclude=(l, t, r, b)` |
|
|
145
182
|
| A black trace is invented or ignored | `--achromatic never` / `always` | `achromatic="never"` |
|
|
@@ -213,14 +250,19 @@ does this for a week of minute-by-minute activity drawn at the size of a small j
|
|
|
213
250
|
that is askew is straightened automatically if the tilt is under five degrees; beyond that,
|
|
214
251
|
or for a photograph taken at an angle (where the axes are no longer parallel), correct the
|
|
215
252
|
image first.
|
|
216
|
-
* One trace per colour per panel. Two traces of the same colour
|
|
217
|
-
|
|
253
|
+
* One trace per colour per panel. Two traces of the same colour and dashed or dotted lines are not
|
|
254
|
+
separated or joined up. Markers are read by `digitise_points`.
|
|
218
255
|
* Where traces cross or overlap, the one drawn underneath is hidden and its values there are
|
|
219
|
-
interpolated (`interpolated` is `True`).
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
256
|
+
interpolated (`interpolated` is `True`). Markers that overlap heavily (the inner turns of a
|
|
257
|
+
spiral on a polar plot, say) are separated by their size, and some may be missed or doubled:
|
|
258
|
+
check the overlay.
|
|
259
|
+
* Axis scales are read only from numeric labels on linear or logarithmic axes (on the left, below,
|
|
260
|
+
or for a colour bar or a second y axis, on the right); dates, month names and broken axes are
|
|
261
|
+
not understood: give `x_ref` / `y_ref` for those. Limits typed in are assumed to be at the axis
|
|
262
|
+
lines unless `y_ref` / `x_ref` say otherwise.
|
|
263
|
+
* Shaded bands are not digitised. A fill drawn with an outline of its own colour (matplotlib's
|
|
264
|
+
`fill_between`) is measured to the middle of the outline; bars are measured to their edges, to
|
|
265
|
+
the nearest pixel where the bars' edges are not antialiased.
|
|
224
266
|
|
|
225
267
|
## Development
|
|
226
268
|
|
|
@@ -244,6 +286,11 @@ ruff check src tests
|
|
|
244
286
|
| `ocr.py`, `summarise.py` | Interval labels along the x axis, and means per interval. |
|
|
245
287
|
| `diagnostics.py`, `provenance.py`, `io.py` | Overlay, record of the run, files. |
|
|
246
288
|
| `validation.py`, `synthetic.py` | Figures drawn from known data, and the comparisons. |
|
|
289
|
+
| `_figure.py` | What every reader does first: the image, straightening, plot areas and axes. |
|
|
290
|
+
| `markers.py`, `points.py` | Markers and their error bars. |
|
|
291
|
+
| `bars.py`, `rows.py`, `heatmap.py`, `polar.py` | Bar charts, stacks of rows, heat maps and polar plots. |
|
|
292
|
+
| `explore.py` | `describe_figure` and `pixels_to_values`, for graphical tools. |
|
|
293
|
+
| `_results.py` | What the readers of points, bars, rows, heat maps and polar plots give back. |
|
|
247
294
|
| `cli.py` | The command line. |
|
|
248
295
|
|
|
249
296
|
Changes are listed in [CHANGELOG.md](https://github.com/comparativechrono/trace-digitiser/blob/main/CHANGELOG.md);
|
|
@@ -23,14 +23,14 @@ This has to be done once by an owner of the `trace-digitiser` project on PyPI.
|
|
|
23
23
|
|
|
24
24
|
3. Click **Add**.
|
|
25
25
|
|
|
26
|
-
That is all.
|
|
26
|
+
That is all.
|
|
27
27
|
|
|
28
28
|
### Optional hardening
|
|
29
29
|
|
|
30
30
|
PyPI recommends tying the publisher to a GitHub *environment*, which lets you
|
|
31
|
-
require a manual approval before each upload.
|
|
32
|
-
|
|
33
|
-
the repository is public
|
|
31
|
+
require a manual approval before each upload. The workflow does not use one
|
|
32
|
+
yet (when the repository was private, GitHub offered environments only on paid
|
|
33
|
+
plans). Now that the repository is public, one can be added:
|
|
34
34
|
|
|
35
35
|
1. Create an environment called `pypi` under *Settings -> Environments*
|
|
36
36
|
(optionally with required reviewers).
|
|
@@ -39,10 +39,12 @@ the repository is public (or on a paid plan):
|
|
|
39
39
|
|
|
40
40
|
## Making a release
|
|
41
41
|
|
|
42
|
-
1. Set `version` in `pyproject.toml
|
|
43
|
-
twice; the
|
|
42
|
+
1. Set `version` in `pyproject.toml`. PyPI never accepts the same version
|
|
43
|
+
twice; the versions already published are listed at
|
|
44
|
+
<https://pypi.org/project/trace-digitiser/#history>.
|
|
44
45
|
2. In `CHANGELOG.md`, give the section for the version its date in place of
|
|
45
|
-
"
|
|
46
|
+
"unreleased", and end its link at the new tag (`...vX.Y.Z` in place of
|
|
47
|
+
`...HEAD`).
|
|
46
48
|
3. Merge to `main` and wait for the *Tests* workflow to pass.
|
|
47
49
|
4. On GitHub: *Releases -> Draft a new release*, create the tag `vX.Y.Z`
|
|
48
50
|
(matching the version exactly, with a leading `v`) on `main`, and publish.
|
|
@@ -53,8 +55,7 @@ uploads to PyPI. If the tag and version disagree, or a test fails, the run
|
|
|
53
55
|
stops before anything is uploaded.
|
|
54
56
|
|
|
55
57
|
The README is shown on PyPI as it is in the release. Its links to files in
|
|
56
|
-
this repository are absolute, so they work there
|
|
57
|
-
public.
|
|
58
|
+
this repository are absolute, so they work there too.
|
|
58
59
|
|
|
59
60
|
## Checking a build without releasing
|
|
60
61
|
|
|
@@ -4,8 +4,8 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "trace-digitiser"
|
|
7
|
-
version = "2.
|
|
8
|
-
description = "Recover the data from
|
|
7
|
+
version = "2.1.0"
|
|
8
|
+
description = "Recover the data from figure images - lines, points, bars, actograms, heat maps - on your own computer: no uploads, no AI model."
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
license = "MIT"
|
|
11
11
|
license-files = ["LICENSE"]
|
|
@@ -20,6 +20,10 @@ keywords = [
|
|
|
20
20
|
"time series",
|
|
21
21
|
"circadian",
|
|
22
22
|
"actigraphy",
|
|
23
|
+
"actogram",
|
|
24
|
+
"heat map",
|
|
25
|
+
"scatter plot",
|
|
26
|
+
"bar chart",
|
|
23
27
|
]
|
|
24
28
|
classifiers = [
|
|
25
29
|
"Development Status :: 4 - Beta",
|