Draw a 2D parameter field by hand.
pip install git+https://github.com/zzzzswh/medium-painter
mepa uiThat is the whole install. The editor is plain HTML/CSS/JS packed in the wheel: no Node, no build step, no network access at runtime, so it works on an offline server over an SSH tunnel.
I do geophysics research and constantly need subsurface velocity models. There are plenty of polished, AI-generated models around now, but a model I draw myself still says exactly what I mean, so I wrote this tool to help me make 2D velocity models.
Then it occurred to me that other fields have the same need: heat conduction, electromagnetics, fluids, all start with a drawn medium. So, with some help from AI, I tidied it into a web tool that is not tied to any discipline and runs on a server. What the fields are called and what they mean is up to you.
It is 2D only for now. 3D may come later. If you have ideas or suggestions, please let me know. Thanks!
A model = a grid + some curves + some seed points + some fields.
- Curves divide the plane into regions. Segments, polylines, splines, polygons, rectangles, circles, ellipses, parabolas, hyperbolas and freehand strokes, all defined by draggable control points.
- Seed points give an enclosed area its values. Double-click an area and type a value for each field. Move a curve and the fill follows, because the fill remembers a physical coordinate, not a region number.
- Fields are the arrays you export. Add one with the
+above the canvas, name it, give it a unit and a background value. Any number of fields share the same geometry.
The project file holds the curves and the rules, never a raster, so it is a
few kilobytes whatever the grid. Changing nx and nz moves nothing: the same
file exports at 601 × 301 for a quick test and 12001 × 6001 for the real run.
It goes in git, it diffs, it can be attached to a paper.
Pick a tool and click on the canvas. Multi-point curves (polyline, spline, polygon) keep taking points; Enter finishes, Esc abandons. Points snap to crossings, control points, other curves and the model edge, so what should seal does seal; hold Ctrl to turn snapping off for a moment.
Double-click an enclosed area and fill in a value for each field in the inspector on the right. Double-click means nothing else on the canvas.
A region's value can be given four ways: constant, one number;
interpolate, linear between the region's own top and bottom, following the
boundary as it changes; expression, a formula in x, z and the other
fields' names; inherit, the field's own derivation rule.
Areas without a seed point are outlined in amber rather than silently filled with the background.
Every shape has a level. A closed shape on a higher level erases whatever lower levels drew inside it; shapes on the same level leave each other alone.
Put interfaces on level 0 and an inclusion on level 1, and the interfaces stop at its edge, leaving the inclusion one region. Otherwise curves running across the model would slice it into pieces. + level on the right creates a level; new shapes land on the top one, and existing shapes can be dragged to another level in the layer list.
Select some curves (Shift to add more) and press s: every place they cross
becomes a cut. Delete the pieces you do not want.
A piece shares the original control points over a narrower parameter range, so a cut parabola is still exactly that parabola, not a polyline.
The Field panel on the left manages the field being shown: name, unit,
background value, colormap and range. A field can also have a derivation
rule, such as rho * cp, and be computed from the others; a value set on a
region overrides the rule. Expressions are parsed against a whitelist, never
evaled, so files are safe to pass around.
Export in the top bar writes the current field at full grid size as raw
float32, either as a download or straight to a path on the server. mepa render on the command line exports every field at once and can write .npy.
Arrays are always (nz, nx). Whether the bytes in a raw file run down a column
or across a row is set by fast_axis, and the layout actually written is
printed on export, because that is the number-one way a model ends up
transposed in someone else's code.
Everything the editor does is a library call:
import mepa
m = mepa.Model.load("examples/composite_plate.mepa.json")
m.grid.nx, m.grid.nz = 4001, 2001 # same geometry, ten times the sampling
fields = mepa.render(m) # {"k": (nz, nx) array, "rho_cp": ...}
mepa.write_binary("k.bin", fields["k"], fast_axis="z")mepa info examples/composite_plate.mepa.json
mepa check examples/composite_plate.mepa.json # seed collisions, unassigned areas, bad expressions
mepa render examples/composite_plate.mepa.json --field k -o k.binThe backend is stateless; the browser holds the whole project and sends it with every request (it is a few kilobytes).
mepa ui --host 127.0.0.1 --port 8000 --no-browser # on the server
ssh -L 8000:localhost:8000 user@cluster # on your laptopThis is a single-user tool with no authentication, and Export can write to any
path. Keep it on 127.0.0.1 and tunnel in; do not bind it to an interface
other people can reach.
The core does not know what a field means; anything that does lives in
mepa.contrib and is only reachable from the command line. There is one such
extra today: with deepwave installed, mepa shot runs one acoustic shot through the field you name as the velocity, which
is a quick way to see whether a model is what you meant.
pip install 'medium-painter[deepwave] @ git+https://github.com/zzzzswh/medium-painter'
mepa shot examples/salt_dome.mepa.json --vp vp --freq 10 --png shot.png| Editor reference | Every tool, panel and shortcut |
| File format | What a .mepa.json contains |
mepa-1.1.schema.json |
The same rules, for machines and editors |
| Design notes | Why it works this way, and what was rejected (中文) |
examples/ |
A heat-conduction plate and a seismic section |
git clone https://github.com/zzzzswh/medium-painter
cd medium-painter
pip install -e ".[dev]"
mepa ui
pytestIf a model made with this ends up in a paper, CITATION.cff has the details,
and GitHub's "Cite this repository" button will format it for you.
MIT licensed.




