# Reverse Plot Template

> Use when a user provides a raster image, screenshot, exported scientific figure, or PDF and asks to reverse engineer, reproduce, digitize, pixel-match, or generate editable OriginPro, Matplotlib, MATLAB, PNG, or CSV artifacts.

- Skill: `guoweimse/reverse-plot-template` (Agent Skill, multi-file: 3 files)
- Install (CLI): `npx skillmds@latest add guoweimse/reverse-plot-template`
- Raw SKILL.md: https://api.skillmd.com/api/skills/guoweimse/reverse-plot-template/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Data & Analytics
- Author: GuoWeimse (https://skillmd.com/u/guoweimse)
- Updated: 2026-09-22
- Page: https://skillmd.com/skills/guoweimse/reverse-plot-template

---


# Reverse Plot Template

## Overview

Reverse engineer a scientific plot from a bitmap or PDF into reproducible artifacts and template parameters:

- A runnable per-plot Python reconstruction script and rendered PNG.
- Editable point/curve CSV files when data must be digitized from pixels.
- A pixel-calibrated OriginPro `.opju` project and Origin-exported PNG when OriginPro automation is available.
- Origin, Matplotlib, and MATLAB parameter tables/snippets when the user asks for templates rather than files.

Always state that reconstructed data are estimates unless source data are available. For PDFs, distinguish vector measurements from rasterized estimates.

## Workflow

1. Identify the input type and whether the user asks for parameter advice only or direct reconstruction files. If unspecified, produce all direct reconstruction artifacts plus the three parameter recipes.
2. For PDF input, follow **PDF Input Workflow** to locate the figure, render or extract it, and create `<slug>_source_crop.png`. For image input, use the supplied image directly.
3. Run `scripts/analyze_plot_bitmap.py` on the source image or PDF figure crop:

```bash
python <skill_dir>/scripts/analyze_plot_bitmap.py "<image_path>" --markdown work/plot_bitmap_analysis.md --json work/plot_bitmap_analysis.json
```

4. Inspect the analysis and source visually. Correct automation mistakes such as text counted as line color, missing borders, panel confusion, or cropped labels.
5. Digitize or estimate the visible data. Preserve separate arrays for fitted curves, observed points, error bars, and each panel when the source distinguishes them.
6. For a direct reconstruction request, create and run the per-plot artifact script described below. Do not stop at a code snippet or parameter table.
7. For an OriginPro recreation request, use the pixel-calibrated workflow below instead of Origin's default page/template scale.
8. Return artifact paths, measured dimensions, uncertainty, and only the requested template details.

## PDF Input Workflow

Treat a PDF as a container, not as an image. A vector PDF has no intrinsic pixel size, so pixel-level matching is defined at a fixed render DPI and crop rectangle.

1. Inspect document metadata and page count with `pdfinfo`. Use `pdftotext`, `pypdf`, or `pdfplumber` only to locate the figure number and caption; never infer layout from extracted text alone.
2. Map the requested figure to candidate pages, then render those pages for visual confirmation. Poppler page numbers are 1-based; PyMuPDF page indices are 0-based.

```bash
pdfinfo "<input.pdf>"
pdftoppm -f <page> -l <page> -singlefile -r 300 -png "<input.pdf>" "work/<slug>_page"
```

3. If Poppler is unavailable or fails on a non-ASCII Windows path, copy the PDF to a short ASCII temporary path without modifying the source, then retry. Use PyMuPDF as the fallback renderer:

```python
import fitz

doc = fitz.open(input_pdf)
page = doc[page_number_1_based - 1]
clip = fitz.Rect(x0_pt, y0_pt, x1_pt, y1_pt)
pix = page.get_pixmap(matrix=fitz.Matrix(dpi / 72, dpi / 72), clip=clip, alpha=False)
pix.save(source_crop_png)
```

4. Inspect `page.get_drawings()`, `page.get_text("dict")`, and `page.get_images()` when available. Use vector objects and embedded text to measure strokes, colors, fonts, and positions; use the rendered crop for final visual calibration. Do not assume the full figure is one embedded image.
5. Determine the graphical figure bounding box in PDF points. Exclude the caption unless the user asks to reproduce it. Render that exact rectangle to `outputs/<slug>_source_crop.png` at a fixed DPI, and record the page number, DPI, PDF-point crop, and resulting pixel size in `<slug>_recreate.py`.
6. For a multi-panel figure, record one pixel rectangle per panel and recreate each panel as a separate Matplotlib axes and Origin graph layer. Include inset axes as independent editable layers.
7. Run the bitmap analyzer on the crop, then use the normal measurement, digitization, OriginPro, Matplotlib, and MATLAB workflows below.
8. If the PDF is scanned or the figure is raster-only, state that all geometry, text, and data are raster estimates. If vector objects are recoverable, state which parameters came from vectors and which were visually inferred.

PDF verification contract:

- Re-render the same PDF crop at the declared DPI before calibration.
- Match the reconstruction canvas to the crop pixel dimensions exactly.
- Verify every panel rectangle, legend, inset, annotation, and visible curve.
- Keep the reconstruction editable; never use the crop as a hidden background substitute.

## Direct Reconstruction Artifacts

Unless the user explicitly asks for parameters only, create these files in the workspace `outputs/` directory using a short slug for the figure:

```text
<slug>_recreate.py
<slug>_digitized_points.csv
<slug>_fitted_curves.csv
<slug>_recreated_python.png
<slug>_recreated_originpro.opju
<slug>_recreated_originpro.png
```

For PDF input, also keep `outputs/<slug>_source_crop.png` as the fixed-DPI audit reference. A rendered full page is temporary unless it is needed to document ambiguous figure boundaries.

Put all per-figure data arrays, colors, geometry, Matplotlib rendering, and optional OriginPro automation in `<slug>_recreate.py`. Use `PchipInterpolator` or another shape-preserving fit for smooth curves when the bitmap shows fitted curves; do not make the curve pass through visibly off-curve observations merely to reduce code.

Implement two paths in that script:

```text
python <slug>_recreate.py             # write CSV files and the Python PNG
python <slug>_recreate.py --origin    # additionally write the .opju and Origin PNG
```

Use Matplotlib with an explicit `figsize=(width_px/dpi, height_px/dpi)`, DPI, and `subplots_adjust` values derived from the plot frame. Use the same digitized arrays for OriginPro. When `originpro` is importable and OriginPro automation succeeds, create a workbook, add fitted curve plots, pale error-bar plots, dark point plots, configure the calibrated page/layer, export the Origin PNG, and save the `.opju`.

If OriginPro automation is absent or fails, still emit the Python script, PNG, CSVs, and an Origin parameter table, but do not claim that an `.opju` was created. Do not embed the source bitmap as a background merely to claim a pixel-level editable reconstruction.

After generation, run both script paths when OriginPro is available. Verify that the PNG dimensions equal the target image or PDF crop, the required artifact files are nonempty, and every Origin plot frame lands at its measured pixel rectangle. Inspect both Python and Origin exports before handoff.

## Measurement Rules

- Use the plot frame as the coordinate reference whenever it is detected. Report frame left/right/top/bottom in pixels and percentages of the image.
- Convert label and guide positions to data coordinates only when axis limits are readable or inferable. Otherwise report them as layer percentages.
- Estimate line widths from rendered pixels and convert to points with `pt ~= px * 72 / dpi`. If DPI is unknown, use the visible relation: `2 px` in a 300-600 px wide bitmap usually corresponds to `1.2-1.8 pt` in Origin/Matplotlib/MATLAB.
- Prefer exact sampled RGB colors from the image for curve colors. Report both RGB and hex.
- Treat font family as an inference. For scientific plots with sans-serif glyphs, start with `Arial` for Origin and MATLAB, and `Arial`/`DejaVu Sans` for Matplotlib.
- Estimate font sizes by bounding-box height:
  - Axis title: usually `14-18 pt`.
  - Tick labels: usually `9-12 pt`.
  - Curve labels and annotations: usually `8-11 pt`.
- Distinguish the objective measurement from the recommendation. Example: "measured curve stroke is about 2 px; set Origin line width to 1.5 pt."

## Origin Template Output

Use this structure when returning Origin settings:

```text
Page and Layer
- Page size: <width> mm x <height> mm, or keep original aspect ratio <W:H>
- Layer position: Left <percent>%, Top <percent>%, Width <percent>%, Height <percent>%
- Background: White
- Border/frame: show left/right/top/bottom, black, <pt> pt

Axes and Ticks
- X scale: From <min> To <max>, Major increment <step>
- Y scale: <hidden/readable/inferred>
- Tick direction: Out
- Major tick length: <pt> pt
- Tick/axis width: <pt> pt
- Tick label font: Arial, <pt> pt
- X title / Y title: text, font, size, style

Plots
- Plot name: color <hex> RGB(...), line width <pt>, symbol none
- Repeat for each curve

Guide Lines and Annotations
- Guide line: x=<data or layer %>, y start/end, black, width <pt>, dash pattern
- Text: content, font, size, position, alignment

Export
- Pixel size or DPI, anti-aliasing, transparent background if needed
```

For Origin rich text, use common escape forms when helpful:

- Greek chi: `\g(c)`
- Superscript: `\+(text)`
- Subscript: `\-(text)`
- Italic variable: `\i(text)`

## Pixel-Calibrated OriginPro Workflow

Use this workflow when the user asks for a faithful editable OriginPro reconstruction, especially after an initial Origin graph has visibly wrong font sizes, stroke widths, or page proportions.

1. Isolate the chart page from any palette or caption below it. Record the target `width_px`, `height_px`, and plot frame pixel rectangle.
2. Choose a fixed export DPI. Use the source DPI when known; otherwise start at `180`. Compute `width_in = width_px / dpi` and `height_in = height_px / dpi`.
3. Activate the new Origin graph, then unlock its default page ratio before setting physical dimensions. Keep graph elements independent of layer resizing:

```python
op.lt_exec(
    f"page.autoSize=0; page.kar=0; layer.fixed=1; "
    f"page.width=page.resx*{width_in}; "
    f"page.height=page.resy*{height_in};"
)
```

`page.kar=0` is required. If it remains enabled, Origin preserves its default page ratio and an export specified by width will have the wrong height. Do not set `page.width` and `page.height` to pixel counts; they are printer dots.

4. Set `layer.unit=1` and apply the measured layer position as percentages. Set page dimensions before this step. For a frame `(left, top, right, bottom)` in a `width_px x height_px` target:

```text
Left   = 100 * left / width_px
Top    = 100 * top / height_px
Width  = 100 * (right - left) / width_px
Height = 100 * (bottom - top) / height_px
```

5. Set all text categories separately. At minimum set Arial (or the inferred font) for x/y tick labels, x/y titles, legend, and free annotations. Do not rely on the active Origin theme to propagate fonts.
6. Set curves with Origin's internal width conversion: `plot.set_cmd(f"-w {line_width_pt * 500}")`. Set axis thickness separately with `layer.x.thickness` and `layer.y.thickness`. Re-export after changing `layer.tickL` or `layer.tickW`; those properties are version-sensitive and should be verified visually rather than converted blindly.
7. When error bars must be pale while points are dark, draw two scatter layers per series: a pale Y-error layer with `-k 0` (no symbol), then a dark circle-only layer without `colyerr`. This avoids Origin coupling the error-bar and marker colors.
8. Build a custom legend when its square swatches or spacing differ from data symbols. Use independent legend symbols, no background, and scale attachment:

```python
legend.text = (
    r"\L(O Shape:Square, Interior:Solid, Style:s, Gap:0, "
    r"Fill:#F47F1E, EdgeColor:#F47F1E, Size:10, EdgeWidth:0) Label"
)
op.lt_exec(
    "legend.font=font(Arial); legend.fsize=11; legend.background=0; "
    "legend.attach=2; doc -uw; "
    "legend.x=layer.x.from+legend.dx/2+inset; "
    "legend.y=layer.y.to-legend.dy/2-y_offset;"
)
```

Use one independent `\L(O ...)` entry per series with its sampled hex color. Tune `legend.vgap`, `inset`, and `y_offset` from the measured bitmap.

9. Export at the target pixel width and verify the resulting pixel size. A width-only export must produce the expected height after page-ratio calibration:

```python
from PIL import Image

graph.save_fig(str(output_png), type="png", width=width_px)
assert Image.open(output_png).size == (width_px, height_px)
```

If the dimensions do not match, stop and correct `page.kar`, physical page dimensions, or export settings before tuning fonts and lines.

## Matplotlib Output

Provide a runnable starting snippet when the user wants Matplotlib or both targets:

```python
import matplotlib.pyplot as plt

plt.rcParams.update({
    "font.family": "Arial",
    "axes.linewidth": 1.4,
    "xtick.major.width": 1.2,
    "ytick.major.width": 1.2,
    "xtick.major.size": 4,
    "ytick.major.size": 4,
    "xtick.direction": "out",
    "ytick.direction": "out",
    "mathtext.fontset": "stix",
})

fig, ax = plt.subplots(figsize=(width_in, height_in))
fig.subplots_adjust(left=..., right=..., bottom=..., top=...)
ax.plot(x, y, color="#...", lw=...)
ax.set_xlabel(r"...", fontsize=...)
ax.set_ylabel(r"...", fontsize=...)
```

Map the detected layer rectangle to `subplots_adjust`:

- `left = frame_left / image_width`
- `right = frame_right / image_width`
- `bottom = 1 - frame_bottom / image_height`
- `top = 1 - frame_top / image_height`

## MATLAB Output

Provide a runnable starting snippet when the user wants MATLAB or all targets:

```matlab
fig = figure('Units','inches', ...
    'Position',[1 1 width_in height_in], ...
    'Color','w');

ax = axes(fig, 'Units','normalized', ...
    'Position',[left bottom width height], ...
    'Box','on', ...
    'LineWidth',1.4, ...
    'TickDir','out', ...
    'TickLength',[0.018 0.018], ...
    'FontName','Arial', ...
    'FontSize',11, ...
    'XColor','k', ...
    'YColor','k');
hold(ax, 'on');

plot(ax, x, y, 'Color',[r g b], 'LineWidth',1.5);
xlabel(ax, 'R + \DeltaR (\AA)', 'FontName','Arial', 'FontSize',16, 'Interpreter','tex');
ylabel(ax, '|\chi(R)| (\AA^{-4})', 'FontName','Arial', 'FontSize',16, 'Interpreter','tex');

plot(ax, [xguide xguide], [y0 y1], 'k--', 'LineWidth',1.2);
text(ax, xpos, ypos, 'label', 'FontName','Arial', 'FontSize',10, 'Interpreter','tex');

exportgraphics(fig, 'recreated_plot.png', 'Resolution',300);
```

Map the detected layer rectangle to MATLAB normalized axes position:

- `left = frame_left / image_width`
- `bottom = 1 - frame_bottom / image_height`
- `width = (frame_right - frame_left) / image_width`
- `height = (frame_bottom - frame_top) / image_height`

MATLAB conversion notes:

- Convert hex/RGB 0-255 colors to MATLAB RGB triplets by dividing each channel by `255`.
- Use `axes(...,'Box','on')` for a full four-sided frame.
- Use endpoint `plot([x x],[y0 y1],...)` instead of `xline` when dashed guide lines stop inside the plot.
- Use the built-in `tex` interpreter for common scientific labels such as `\chi`, `\Delta`, superscripts, subscripts, and `\AA`. Use `latex` only if the original clearly uses LaTeX-style math.
- Prefer `exportgraphics` for modern MATLAB. If matching older MATLAB, use `print(fig,'-dpng','-r300',filename)`.

## Quality Checklist

Before answering, verify that the output includes:

- Image size and detected plot frame.
- For PDF input: source page, fixed DPI, PDF-point crop, source crop PNG, and vector-versus-raster provenance.
- For a direct reconstruction request: a runnable Python script, Python PNG, digitized CSVs, and, when automation is available, a verified `.opju` and Origin PNG.
- Axis limits/ticks if readable.
- Line colors and line widths for all visible curves.
- Font family and approximate font sizes.
- Annotation and dashed guide-line styling.
- Origin, Matplotlib, and MATLAB settings unless the user requested only one or two targets.
- For OriginPro pixel calibration: unlocked page ratio, fixed element scaling, target DPI, verified export size, and separately calibrated text/axis/curve/error-bar/legend styling.
- For multi-panel PDF figures: one measured rectangle and editable layer per panel and inset.
- A clear approximation note and any uncertainty caused by low resolution, anti-aliasing, cropping, or missing source data.

