English | 简体中文
Publication-grade pipeline, framework and teaser figures for AI conference papers.
Write a short spec — or point it at your model code — and get a figure that is
natively editable in Visio and PowerPoint, plus PDF for LaTeX and 600 dpi PNG.
Quickstart · Install · From code · Templates · Visio · Auditor · FAQ
Machine-made framework figures are not ugly. They are generic, and reviewers recognise them in about a second:
| The tell | What a real conference figure does instead |
|---|---|
| Saturated rainbow fills, one hue per box | 3–5 pale tints, each meaning exactly one thing |
| Drop shadows, gradients, glassmorphism | Flat fills, one black hairline outline |
| Gears, brains, clouds, sparkles, emoji | Tensors as slabs, encoders as trapezoids |
| Whole sentences inside boxes | Noun phrases, six words or fewer |
| Identical boxes on a perfect grid | Widths that encode importance |
| Floating curved arrows that miss their ports | Orthogonal runs on port normals |
| No real data anywhere | Real input crops and real output renders, embedded |
| "Figure 1: Overview of the proposed framework." | A caption that states a claim |
Every one of those is a default. CVPR-Figure removes them from the engine so
they cannot come back by accident:
- the palette, type sizes, stroke weights, corner radii and padding were
measured out of the vector content of 349 published figure PDFs, not
invented — the full audit trail, including three rules that were tested and
cut for lack of evidence, is in
references/corpus-report.md; - layout is solved deterministically in final printed points —
size: 7.0in the spec is 7.0 pt on the printed page, with no mental arithmetic; - arrows are forced to leave and enter along port normals and turn at right angles;
- an auditor must pass before you ship, and it names each tell individually.
v2 re-derived every constant from a wider corpus: 46 papers with complete arXiv sources from three groups whose figures this project is trying to sound like — Tsinghua MARS, Wenzhao Zheng's group, and Megvii — covering 292 figure environments and 349 figure PDFs, read with PyMuPDF.
Almost nobody draws in TikZ. One of 290 figures uses tikzpicture, and it
is an ablation bar chart, not a diagram. Every architecture figure in the
corpus is external artwork placed with \includegraphics, and 86% are a
single file — the
composition happens in the drawing tool, not in LaTeX. The embedded font tables
say which tool: Calibri and Cambria Math (PowerPoint/Visio), Segoe Print
(Windows), Times New Roman PS MT set by hand. So generating a native
.pptx/.vsdx with an Office palette is not a stylistic preference — it is a
reconstruction of the actual production pipeline.
The palette is three colour pickers, not one. 101 colours appear in four or
more distinct figures, and they resolve cleanly into the Office 2013+ theme
ladder, the diagrams.net stock styles (which ship as fill/stroke pairs), and
matplotlib's tab10 confined to plot panels. palettes.py keeps all three
whole rather than blending them:
#DEEBF7 #BDD7EE #9DC3E6 #5B9BD5 #2E75B6 Office Blue Accent 5, verbatim
#E2F0D9 #C5E0B4 #A9D18E #70AD47 #548235 Green Accent 6
#FFF2CC #FFE699 #FFD966 #FFC000 #BF9000 Gold Accent 4
#DAE8FC / #6C8EBF #D5E8D4 / #82B366 draw.io pairs -- never split
strokes: 45% of all stroke length is pure #000000
Type is smaller than anyone admits. Applying each figure's own canvas-to-column scale factor (median 0.385), the rendered glyph distribution has a median of 6.0 pt and quartiles at 4.5 and 7.5. The median smallest glyph in a figure is 5.4 pt. That set the auditor's 5.0 pt floor — and it also showed v1's stroke weights were about 2× too heavy, since the real rendered modes are 0.19, 0.36, 0.45 and 0.80 pt.
Framework figures contain real data. 57% of the architecture diagrams embed
at least one raster — median 11 of them, about a quarter of the canvas — and
66% of those put images at both the far left and the far right. Inputs in,
predictions out, architecture between. That is why image, imagegrid and
cameraring exist, why image takes its aspect ratio from the real file, and
why the auditor nudges a pipeline drawn as boxes alone.
No install, no dependencies. Clone and render:
git clone https://github.com/ChizkiyahuOhayon/CVPR-Figure.git
cd CVPR-Figure
python3 scripts/render.py examples/quickstart.yaml -o build/quickstart \
-f svg,pdf,png,pptx,vsdx --dpi 600wrote build/quickstart.svg
wrote build/quickstart.pdf
wrote build/quickstart.png
wrote build/quickstart.pptx
wrote build/quickstart.vsdx
pdf via libreoffice
png via pdftoppm @600 dpi -- 4147 x 1530 px
pptx: 44 shapes, 0 embedded image(s)
canvas 496.8 x 182.8 pt | 10 nodes | 8 edges | scale 1.000
That came from examples/quickstart.yaml, which is
about 50 lines. Open build/quickstart.pptx in PowerPoint or
build/quickstart.vsdx in Visio and drag a box: everything is a native
shape, no ungrouping required.
Then check it:
python3 scripts/validate.py examples/quickstart.yaml --svg build/quickstart.svg
# 0 error(s), 0 warning(s), 0 note(s) | 497 x 183 pt | 10 nodesgit clone https://github.com/ChizkiyahuOhayon/CVPR-Figure.git
cd CVPR-Figure
./install.sh # -> ~/.claude/skills/cvpr-figure (all projects)
./install.sh --project # -> ./.claude/skills/cvpr-figure (this repo only)The installer copies the skill, runs a self-test, and reports which optional converters it found.
The project is CVPR-Figure, but the installed skill id is
cvpr-figurein lowercase — Claude Code skill ids are lowercase-kebab, so that is what you type after the slash.
Then just ask, in plain language:
Read
method.texand draw the overview figure for a CVPR submission, full width. Export a PDF for LaTeX and an editable.vsdx.
Claude Code loads SKILL.md, fills in the figure contract, picks an
archetype, writes the spec, renders, audits and hands back the LaTeX block.
You can also invoke it explicitly with /cvpr-figure.
Point the agent at AGENTS.md — the same instructions without the
Claude Code routing metadata:
git clone https://github.com/ChizkiyahuOhayon/CVPR-Figure.git third_party/CVPR-Figure
# then, in your AGENTS.md / .cursorrules / system prompt:
# "For any framework, pipeline, teaser or module figure,
# follow third_party/CVPR-Figure/AGENTS.md."agents/openai.yaml carries the display name and default prompt for hosts that
read it.
The engine is a normal CLI. Copy the template closest to your figure, edit the
YAML, render. Everything in references/spec-language.md.
| Required | Python 3.8+. That is all — the engine is standard library only. |
| Optional | PyYAML (a bundled parser covers the same subset, byte-identically, when it is absent) |
For .pdf |
any one of Inkscape, LibreOffice, rsvg-convert, CairoSVG |
For .png / .tiff |
pdftoppm (poppler-utils) or ImageMagick |
For .emf |
Inkscape or LibreOffice |
.svg, .vsdx and .pptx are written by bundled code and always work.
Installing the optional converters
# macOS
brew install --cask inkscape libreoffice
brew install poppler imagemagick
# Debian / Ubuntu
sudo apt install inkscape libreoffice-draw poppler-utils imagemagick librsvg2-bin
# minimal: poppler + librsvg is enough for PDF and high-resolution PNGThree steps. The skill walks an agent through all three; by hand you do the same.
static/core/contract.md asks seven questions. The
first one does most of the work:
This figure makes the reader believe: ______________________
If the sentence needs an "and", it is two figures. If it is "this is the architecture of our method", that is a description, not a claim, and it produces a figure with nothing to emphasise.
You also fix the archetype, the one element that is the contribution, the semantic colour map, the venue and column width, and which real crops will fill the image slots.
You state what goes where in what order. The engine decides geometry.
figure: {id: overview, venue: cvpr, width: double, font: times}
layout: row
gap: 14
panels:
- id: s_core
title: "Our Contribution" # bold italic, above a dashed container
frame: dashed
fill: gold.pale
body:
- {id: q, shape: slab, n: 5, cell: 8, cellh: 26, role: attention}
- group: # a tinted sub-region with a repeat badge
id: blk
frame: region
frame_color: gold.deep
badge: "×L"
body:
- {id: b1, text: "Cross-Attention", role: core, outline: match}
- {id: b2, text: "Feed Forward", role: core, outline: match}
edges:
- {from: q, to: blk, color: gold.deep}
- {from: q.w, to: sum.w, route: bus, bend: 10, dash: true, label: "residual"}Two properties do most of the "this looks hand-made" work:
- uniform snapping — box-shaped siblings in a column are snapped to the widest, and in a row to the tallest, so a stack of modules sits on a grid;
- port-normal routing — whatever route you ask for, the line leaves along the source port's normal and arrives along the target's, and turns at right angles. No arrowhead ever lands flat against an edge.
python3 scripts/validate.py spec.yaml --svg build/fig.svgFix every FAIL. Justify or fix every WARN. Then do the eye pass in
references/anti-ai-checklist.md: squint
test, trace one path with a finger, cover the caption, convert to greyscale.
Point it at a model implementation and get a first-draft spec. Nothing is
imported or executed — both readers parse with ast, so there is no
dependency install, no checkpoint download and no CUDA.
# what model classes are in this package?
python3 scripts/from_code.py bevdepth/layers/ --list
# draw one
python3 scripts/from_code.py bevdepth/layers/ --model DepthNet -o fig.yaml
python3 scripts/render.py fig.yaml -o build/depthnet -f svg,pdf,pptxThat is DepthNet straight out of BEVDepth with no hand editing. The reader
recovered the two parallel branches from forward, pulled the nn.Sequential
contents into the captions, pruned the norm/dropout plumbing, and coloured the
branches apart because the module names gave role inference nothing to work
with.
If the model ships an mmengine / mmdet / mmdet3d config, use that instead — it names every stage in pipeline order with its channel widths, and it is a far better source than the class hierarchy:
python3 scripts/from_code.py configs/model.py --mm -o fig.yamlA draft wider than its column gets silently downscaled at render time, and downscaling is exactly how figures end up with 5 pt labels. So the emitter measures its own draft and reshapes it, reporting each concession:
$ python3 scripts/from_code.py configs/sparseworld.py --mm -o fig.yaml
7 nodes, 6 edges, 7 layers from BEVStereo4DOCC
fit: dropped channel captions; abbreviated module names;
dropped 1 peripheral modules to fit the column
In order: drop the channel captions, abbreviate names the way the corpus writes
them (img_bev_encoder_backbone → BEV Enc. Backbone), shed the least
connected boxes, and only as a last resort fold into two bands with a proper
return sweep.
The reader gets the modules, their order and the branch structure right. It has no idea which module is your contribution. Before you ship, expect to:
- rename boxes to the names the paper uses, not the attribute names;
- delete anything the paper does not discuss — the corpus median framework figure has 11 boxes;
- attach real
src:crops to the input and output slots; - check cross-branch arrows against what
forwardactually does; - give the contribution
role: coreand its own panel.
Known limits are written down rather than glossed:
references/from-code.md lists what the reader
flattens (both arms of an if), what it cannot see (functional calls,
_base_ config inheritance) and what it guesses (for blk in self.blocks
becomes ×N, because the count is not in the source).
Copy the one closest to your figure and replace the content. Starting from a blank spec wastes effort and loses the proportions that make an archetype work.
A paper usually needs one teaser-class figure plus one or two others. Needing a third framework figure is almost always a sign that the method has not been factored cleanly.
57% of the framework diagrams in the reference corpus embed rasters, and two thirds of those put images at both ends. These five templates are built around that.
These exist because a measurement paper still has an argument, and the argument is what the figure has to carry.
Full rules for each, including the characteristic failure mode, are in
references/archetypes.md.
python3 scripts/render.py spec.yaml -o build/fig \
-f svg,pdf,png,tiff,emf,vsdx,pptx --dpi 600| Format | Written by | Editable as |
|---|---|---|
.svg |
bundled | Illustrator, Inkscape, Figma, Affinity, browsers |
.vsdx |
bundled | Visio, natively — no ungroup step |
.pptx |
bundled | PowerPoint, natively; pastes into Visio as shapes |
.pdf |
whichever converter is present | LaTeX \includegraphics, Illustrator |
.png / .tiff |
rasterised from the PDF at --dpi |
slides, rebuttals, posters |
.emf |
Inkscape or LibreOffice | Word, and Visio import + ungroup |
Rasters are always taken from the PDF, never straight from the SVG.
LibreOffice's PNG filter silently ignores the requested density and emits a
96 dpi screenshot — a 600 dpi request used to produce a 317 px image. So
render.py rasterises the PDF with a tool that honours a density flag, then
verifies the produced pixel count against --dpi and warns if a converter
cut the corner:
png via pdftoppm @600 dpi -- 4200 x 1857 px
Use 600 dpi for camera-ready and 1200 dpi for a poster.
Every module in build/fig.vsdx is a real Visio shape with its own
geometry, fill, line and character sections:
- click a box and the Format Shape pane shows the fill as
#FFE699; - drag a vertex and the geometry updates — no ungroup;
- edit the text in place and it stays 7 pt Times New Roman;
- arrows are polylines with
EndArrow=4, so their waypoints are draggable.
Raster panels.
imagenodes export to.vsdxas labelled placeholder rectangles, and the render report lists the files it skipped. Use.pptxif you want the crops embedded for you, or drop them in by hand with Insert → Pictures.
Prefer not to write YAML at all? Generate a drag-and-drop palette:
python3 scripts/make_stencil.py -o build/stencil -f vsdx,pptxKeep it open in a second window and copy modules across. They arrive already carrying the house fill, outline, corner radius and 7 pt Times label.
Full round-trip notes in references/visio-workflow.md.
python3 scripts/validate.py spec.yaml --svg build/fig.svg [--strict] [--json]| Code | Level | What it catches |
|---|---|---|
text-too-small |
error | anything under 5.0 pt at final printed size |
label-overflow |
error | text wider than its box |
node-overlap |
error | two siblings occupying the same space |
inconsistent-role |
error | one concept drawn in two different fills |
bad-edge |
error | an edge naming a node that does not exist |
emoji |
error | emoji or dingbats |
edge-crosses-node |
warning | an arrow routed through a third module |
off-palette |
warning | a colour outside the house tint ladders |
too-many-hues |
warning | more than five colour families |
stroke-zoo |
warning | more than four distinct line weights |
mixed-fonts |
warning | two type families in one figure |
label-verbose |
warning | a label over six words |
tall-figure |
warning | over the venue's height budget |
gradient / shadow |
warning | gradients or filters in the SVG |
unconnected |
note | a module no arrow touches |
Exit code is non-zero on any error, or on any warning with --strict — so it
drops straight into CI or a pre-commit hook.
It is not decoration. On a real paper it caught four labels at 5.4 pt, a figure
using six colour families, and a template that drew encoder in two different
fills — all of which would otherwise have shipped.
The auditor cannot judge whether the figure communicates. Render a PNG and look at it.
The engine lays out in final rendered points, and venue + width set the
canvas to the exact column width. So:
\begin{figure*}[t]
\centering
\includegraphics[width=\linewidth]{figures/overview.pdf}
\caption{\textbf{Overview of X.} <the claim>. <the parts, left to right>.}
\label{fig:overview}
\end{figure*}renders at 100 %, and size: 7.0 in the spec is 7.0 pt on the page.
Want it at 80 % of the column? Set width_frac: 0.8 in the spec and keep
width=\linewidth in LaTeX. Writing width=0.8\linewidth instead shrinks your
type to 5.6 pt.
| Venue | \columnwidth |
\textwidth |
|---|---|---|
| CVPR / ICCV / WACV | 237.13 pt | 496.85 pt |
| ECCV (LNCS) | 347.12 pt | 347.12 pt |
| NeurIPS / ICLR | 397.48 pt | 397.48 pt |
| ICML | 234.88 pt | 487.82 pt |
| AAAI | 238.49 pt | 504.00 pt |
| ACL / EMNLP | 219.08 pt | 455.24 pt |
More, including float placement and camera-ready checks, in
references/latex-integration.md.
figures/
overview.pdf <- what LaTeX includes
overview.pptx <- what you hand a co-author
src/overview.yaml <- the spec, committed
src/crops/*.png <- the real data panels
for f in figures/src/*.yaml; do
python3 scripts/render.py "$f" -o "figures/$(basename "${f%.yaml}")" \
-f svg,pdf,pptx --dpi 600
doneSKILL.md Claude Code entry point (frontmatter routing)
AGENTS.md Codex / other-agent entry point
manifest.yaml which fragment to load for which job
install.sh one-command install as a Claude Code skill
static/core/
stance.md the non-negotiables and the list of tells
contract.md the seven questions to answer before drawing
references/
corpus-report.md every measurement, with the rules that were cut
house-style.md the measured palette, type and strokes — and the method
spec-language.md the full spec grammar
archetypes.md the archetypes and what makes each one work
from-code.md drafting from source: what it infers, where it is wrong
case-studies.md published figures taken apart
anti-ai-checklist.md the automated checks plus the eye pass
visio-workflow.md Visio / PowerPoint / Illustrator editing
latex-integration.md sizing, floats, camera-ready checks
scripts/
render.py spec -> svg / pdf / png / tiff / emf / vsdx / pptx
from_code.py PyTorch source or mmdet config -> spec
validate.py the auditor
make_stencil.py the module palette generator
cvprfig/ the engine (standard library only)
palettes.py the three measured colour systems, kept apart
style.py typography, strokes, geometry, roles
text.py baked Adobe Core-14 metrics + inline markup
imgsize.py PNG/JPEG/GIF/PDF dimensions, no dependencies
layout.py the box-model solver
edges.py orthogonal routing
shapes.py the shape vocabulary
svg.py vsdx.py pptx.py three renderers over one layout
miniyaml.py dependency-free YAML subset parser
code2fig/ graph IR, two source readers, spec emitter
graph.py the IR, role inference, pruning, branch colouring
torchscan.py nn.Module source -> graph, via ast
mmconfig.py mmengine/mmdet config -> graph, via ast
emit.py graph -> spec, with the column-fitting ladder
templates/ sixteen archetypes + stencil.vsdx / stencil.pptx
examples/quickstart.yaml the 50-line example above
assets/gallery/ rendered previews
tests/test_engine.py regression checks (engine, exporters, auditor, code2fig)
python3 tests/test_engine.py167 checks, no dependencies, about two seconds. They cover text metrics against a
published box width, the bundled YAML parser (byte-identical output to PyYAML),
layout snapping, port-normal routing, colour and named-stroke resolution, nested
container forms, every shape, all sixteen templates rendering and passing the
auditor, .vsdx / .pptx package structure and shape counts, raster embedding
and real-aspect sizing, both code readers end to end (submodules, dataflow
edges, branch survival, plumbing pruning, role inference, config annotations),
the column-fitting ladder, and the auditor's own positive and negative cases.
CI runs them on Python 3.9, 3.11 and 3.13 with nothing installed, so a regression that introduces a dependency fails the build.
Can it draw bar charts, curves, heatmaps or ablation plots?
No, and it will tell you so. Those are matplotlib/seaborn work. This project is for the figures matplotlib is bad at: architecture diagrams, pipelines, teasers, module zoom-ins, taxonomies.
Does it use an image model?
No. Nothing is generated by diffusion or by an LLM drawing SVG coordinates — both produce exactly the generic look this project exists to prevent. A deterministic solver computes every coordinate, so the same spec always gives the same figure, and a diff of the spec is a diff of the figure.
Will it invent my results?
No. image nodes without a src render as deliberately obvious labelled
placeholders, so an unfinished figure cannot be mistaken for a finished one, and
the render report lists which slots are still empty.
My figure is too wide. What now?
The renderer scales it down and tells you the effective point size:
WARNING: Layout is 539.6 pt wide but the target column is 504.0 pt,
so the figure is scaled to 93%; body labels render at ~6.5 pt.
Below 5.0 pt that becomes an error. The fix is to shorten labels, drop a stage, or split the figure — not to accept the shrink.
Can I edit the .vsdx and get the spec back?
No — there is no reverse parser. Treat the spec as the master while the
structure is still moving, and switch to editing the .vsdx or .pptx only
once the layout has settled.
Fonts look wrong after conversion.
The SVG names Times New Roman, Nimbus Roman, Liberation Serif, Times, serif so
it degrades sensibly, but converters substitute at their own discretion. Check
with pdffonts build/fig.pdf; for exact metrics convert with Inkscape and
--export-text-to-path.
How do I match a specific paper's style?
Read its palette straight out of the published PDF — the method is at the top of
references/house-style.md — then write a spec
with those colours. references/case-studies.md
takes five well-known figures apart idiom by idiom.
Two things share a name but must be different colours.
Usually the right fix is to name them apart — real dual-branch figures write
encoder f_θ and encoder f_ξ, not encoder twice. When the contrast really is
the point, set same_label_ok: true on the node to silence that one check.
The style constants were measured from the vector content of these published
papers' PDFs. Only typographic parameters were extracted — palette, type sizes,
stroke weights, geometry. No graphical content was copied, and everything in
assets/gallery/ was rendered by this project's own templates.
| Paper | Venue |
|---|---|
| SparseWorld: A Flexible, Adaptive and Efficient 4D Occupancy World Model | arXiv:2510.17482 |
| GaussianWorld: Gaussian World Model for Streaming 3D Occupancy Prediction | CVPR 2025, arXiv:2412.10373 |
| EmbodiedOcc: Embodied 3D Occupancy Prediction | arXiv:2412.04380 |
| StreamVGGT: Streaming Visual Geometry Transformer | arXiv:2507.11539 |
| TPVFormer: Tri-Perspective View for 3D Semantic Occupancy Prediction | CVPR 2023, arXiv:2302.07817 |
The measurement method and the full numbers are in
references/house-style.md, so you can repeat it on
any paper whose figures you admire.
Issues and pull requests are welcome. Please make sure
python3 tests/test_engine.py passes and that any new template renders with
zero auditor errors — CI checks both.
Especially useful:
- new archetypes for fields this does not cover yet (NLP, speech, RL, theory, robotics systems);
- venue widths verified against a real
\the\columnwidth; - house-style measurements from other labs' figures.
MIT © 2026 Zhao Liu.
Free to use in your papers, commercially, and to redistribute. No attribution required beyond the licence notice — though a star, or a mention if the figures helped, is always appreciated.


















