Open-source MATLAB toolbox for crystallographic texture analysis — EBSD, pole figures, ODF reconstruction, grain boundaries. No GUI; everything is a function/class API.
Run from the repo root, so startup.m picks up startup_mtex and the path:
matlab -batch "your_command_here"
Startup costs ~10-20 s, much more when another MATLAB is competing for the machine.
Which MATLAB to invoke is developer-local. Install paths, activated releases, container setups and timings differ per machine, so they belong in an untracked
CLAUDE.local.mdat the repo root, not here. PointMATLAB_ROOTat your install rather than hardcoding a path.
MTEX needs the Statistics and Machine Learning Toolbox — knnsearch is called from the
EBSD/grain code. Without it six core tests fail (check_calcGrainsCases, check_ebsd,
check_ebsdImport, check_find, check_gradient, check_jcvoronoi), which looks exactly
like six regressions. Check ver('stats') before believing them.
docs/agents/matlab-bridge/ keeps one headless session warm across many calls, paying
startup once:
docs/agents/matlab-bridge/setup.sh # once: Python 3.12 venv + MATLAB Engine API
docs/agents/matlab-bridge/start_session.sh
docs/agents/matlab-bridge/.venv/bin/python docs/agents/matlab-bridge/matlab_run.py "cmd"
docs/agents/matlab-bridge/stop_session.sh
matlab_run.py mirrors -batch semantics — nonzero exit on error, live stdout — and clears
the base workspace per call while keeping the path warm. Compute time is identical to
-batch; the difference is startup and that the R2024b teardown hang below cannot strand a
run. It never touches the developer's own interactive session.
Caveats: a new .m file or folder needs a session restart (the path is fixed at
startup; edits to existing files are picked up immediately), a classdef edit needs one
too, and the session holds a license checkout — stop it when not iterating.
R2024b teardown hang. After a large calcGrains/jcvoronoi2 workload or a full
runTests, MATLAB prints free(): chunks in smallbin corrupted at exit and hangs in
teardown with every result already computed — so a -batch run looks like slow computation
rather than a finished one. R2024a and R2026a are clean; see issue #2589. Guard -batch
jobs with timeout, read a hang after the last line of output as this, and prefer the
bridge, where it does not occur.
MEX and libstdc++. The committed mex/*.mexa64 are built against a newer libstdc++ than
some releases bundle and fail with GLIBCXX_3.4.32 not found, typically in a container.
Rebuilding is the fix, but mex_install compiles in place and would overwrite the
checked-in binaries — build out-of-tree and addpath(...,'-begin') instead.
Class-per-folder: @ClassName/ holds a class's methods as separate .m files, e.g.
geometry/@vector3d/angle.m. Each folder below has its own CLAUDE.md with the detail.
geometry/— vectors, orientations, Miller indices, symmetries, reference framesEBSDAnalysis/— EBSD data, grains, grain boundaries, reconstructionPoleFigureAnalysis/— pole figure data and ODF-from-pole-figure solversSO3Fun/,S2Fun/,S1Fun/— function spaces on SO(3), the sphere, the circleTensorAnalysis/— elastic/plastic tensor calculationsplotting/— all figure and plot codeinterfaces/— file format import/exportdoc/— published example scripts, and the source of the online documentationextern/— vendored third-party code (jsonlab, NFFT, jcvoronoi, …)mex/— compiled MEX binaries, checked in, not rebuilt by defaultobsolete/,old/,compatibility/— deprecated wrappers that warn and forward. Never a reference for current patterns.
Line endings are LF, enforced by .gitattributes (*.m text eol=lf). extern/** and
data/** are marked -text and keep their bytes, because importers parse fixtures byte by
byte and some carry deliberately odd endings — never "fix" a line ending under data/.
This is the rule agents break most often here, in code and in prose alike. Change the
line, the number, the sentence — and stop. What the previous version said, why it was
wrong, how the new value was measured, what would break without it: all of that belongs in
the commit message, an issue, or an ADR under docs/adr/. None of it belongs in the file.
It takes two shapes, and the second is harder to catch because it sounds like precision.
- A comment justifying the edit. What the previous implementation did, which MTEX version changed it, which commit or dataset a number was measured on, how many grains some map gained. None of it helps the next reader and all of it ages badly — the repository was swept once to remove ~2800 such lines.
- Prose arguing with the text it replaced. A doc page that said "the map has turned by a quarter turn" was corrected to "The picture is mirrored rather than turned" — but the reader never saw the quarter turn, so the sentence contradicts nobody and reads as a non sequitur. From the same sweep: "Take the number rather than the crowding", "The mismatch does not simply accumulate", "this figure has no single largest grain to point at", "since a phase map would not show what the threshold did". Every one had to be rewritten as a plain statement of what is there now.
The test: read the new text as someone who has never seen the old one. If a sentence only works for a reader who has, rewrite it. A negation is fine when it heads off a misreading available from what is in front of the reader — "it is not a percentage of the specimen", "this threshold does not remove notIndexed points" — and wrong when its referent is a sentence you have just deleted.
The same holds for an edit that reverts or supersedes earlier work: the file ends in its new state, not in its new state plus a record of the detour.
A change is measured by the file it leaves behind, not by the diff. Before adding a
branch, a helper, a flag or a guard, look for the version where the defect cannot happen and
the file is shorter: one expression covering both cases instead of an isscalar branch, a
value carried along instead of recomputed, a special case folded into the general one, the
decision made once in the place that already makes it rather than at every call site.
Where a shorter version drops an edge case, say so in one sentence and offer it anyway — less functionality is an acceptable price for significantly shorter and clearer code. Report what the file lost or gained.
One line, saying what the next lines do. Match the surrounding style: lowercase, terse, no full stop, above the statement or trailing on it. If the code is self-explanatory, write no comment. A single line is the budget; two or three only for a genuinely non-obvious invariant or a formula.
Everything is vectorized. One object instance holds an array of many entities — a
vector3d is a point cloud, an EBSD a whole scan, a grain2d every grain in a map — with
its properties as arrays in lockstep. Methods operate elementwise across the array via
operator overloading and broadcasting. Assume this represents N objects and check how
this(idx) is implemented before writing a loop.
- Geometry (
geometry/):quaternion→rotation→orientation, andvector3d→Miller.symmetryis threaded through both. A@referenceFramecarries the axes and the plotting convention, and is the only thing that carries a convention. - EBSD → grains (
EBSDAnalysis/):calcGrainssegments anEBSDinto agrain2d(grain3dfor volume data), whose segments are a separategrainBoundary.parentGrainReconstructorrecovers parent-phase grains from child-phase data. - Function spaces (
SO3Fun/,S2Fun/,S1Fun/): ODFs and spherical data are function objects behind one abstract interface, so harmonic, RBF, triangulated and homochoric representations are interchangeable.SO3VectorField/S2VectorFieldmirror this for vector-valued fields. - Plotting (
plotting/): everything goes throughmtexFigure, which owns multi-axis layout, colorbars and annotations.
Decisions with lasting consequences are recorded in docs/adr/, and the source cites them
by number. ADR 0003 (reference frame vs symmetry) is the one most code refers to.
tests/ holds standalone check_*.m functions, not a matlab.unittest suite, sorted into
tiers. runTests runs a tier, each test in its own try/catch, and raises at the end so
-batch exits nonzero.
matlab -batch "runTests" # core, the fast tier
matlab -batch "runTests('slow')"
Never start a test run without asking first, and never start any run that could take longer than 5 minutes. That means
runTestsin any tier,check_mtex, and any individualcheck_*.m— a tier is minutes of the machine, and the developer may already have a session doing something they care about. Say what you want to run and why, and wait. Targeted commands of your own through the bridge are fine, under the same limit: give every command a timeout that enforces it, and when a check does not fit, find the way to get the same answer inside it — a handful of cases rather than the whole set — instead of raising the timeout.
Read tests/CLAUDE.md before adding a test. It has the tier budgets, which file owns
which subsystem, and when a bug earns a new file at all.
MTEX has a Python port, ../pymtex, developed in parallel, and a Julia port, ../mtexJulia.
../pymtex/tools/graph/ holds the dependency graph of MTEX and pymtex: the call graphs, the
counterparts between the two, the notes that mention a function, and the algorithms with their
profiles. It is generated from the sources and rebuilds itself when they are newer.
cd ../pymtex
.venv/bin/python tools/graph/query.py impact <function> # callers in both ports, tests, benchmarks
.venv/bin/python tools/graph/query.py node <function> # counterparts, callers, notes
.venv/bin/python tools/graph/query.py algo <name> # where the time goes, port by port
Ask impact before changing an algorithm, and afterwards run the ../pymtex/bench/*_ops.py
that covers it: it checks this tree against the Python port on one input and times Julia. A new
check follows that pattern - one input, an ALGORITHM literal, the MATLAB half in
bench/matlab/ - rather than a test aimed at one bug.
- Issues live in GitHub Issues on
mtex-toolbox/mtex, viagh. Seedocs/agents/issue-tracker.md. - Triage labels:
needs-triage,needs-info,ready-for-agent,ready-for-human,wontfix. Seedocs/agents/triage-labels.md. - Domain docs:
CONTEXT.mdanddocs/adr/at the repo root. Seedocs/agents/domain.md.