Skip to main content

MNA / MNX Project Format

The MNA (MNE Node-graph for Analysis) format is a portable project file that bundles data references, processing parameters, and an executable computational graph into a single manifest. It comes in two serialization variants:

MNA (JSON)MNX (Binary)
Extension.mna.mnx
EncodingUTF-8 JSONCBOR with MNX1 magic header
Best forHuman reading, diffs, version controlDistribution, speed, embedded data
Embedded dataBase64-encoded in "data" fieldRaw bytes (zero-copy CBOR)
SchemaIdenticalIdentical

Both formats represent the exact same data model. You can convert freely between them — MnaIO::read() and MnaIO::write() dispatch by file extension.

MNA/MNX solves a fundamental problem in neuroimaging workflows: the scattering of raw data files, intermediate results, parameter choices, and processing scripts across directories with no formal link between them.

Motivation

A typical MEG/EEG analysis involves dozens of files — raw recordings, forward solutions, inverse operators, covariance matrices, source estimates — plus parameter choices (filter cutoffs, regularization values, SNR assumptions) that are often buried in scripts or lost entirely. Reproducing a result means reconstructing the exact sequence of operations and parameters, which is error-prone and labour-intensive.

MNA addresses this by capturing the complete analysis state in a single, machine-readable document:

  • Data inventory — every input and output file is referenced with a path, SHA-256 hash, and semantic role
  • Parameter transparency — all processing parameters are explicit, versioned, and optionally formula-driven
  • Executable graph — the processing pipeline is a directed acyclic graph (DAG) that can be re-executed to reproduce results
  • Provenance — each node records which tool version produced it, when it ran, and what verification checks it passed

Project Hierarchy

An MNA project follows a hierarchical data model that mirrors the structure of a neuroimaging study:

MnaProject
├── name, description, mnaVersion, created, modified
├── subjects[]
│ └── MnaSubject
│ ├── id, freeSurferDir
│ └── sessions[]
│ └── MnaSession
│ ├── id
│ └── recordings[]
│ └── MnaRecording
│ ├── id
│ └── files[]
│ └── MnaFileRef
│ ├── role (Raw, Forward, Inverse, …)
│ ├── path (relative POSIX)
│ ├── sha256
│ ├── format ("fiff", "stc", "mgh", …)
│ ├── sizeBytes
│ └── embedded (bool + data)
└── pipeline[] → MnaNode[] (the computational graph)

Class Reference

ClassPurpose
MnaProjectTop-level container. Holds subjects, the processing pipeline, and project metadata. Schema version is stored in mnaVersion (current: "1.0").
MnaSubjectRepresents a participant. Links to a FreeSurfer SUBJECTS_DIR for anatomy.
MnaSessionGroups recordings from one measurement session.
MnaRecordingGroups files from one recording run (raw, events, head digitization, etc.).
MnaFileRefA single file reference with role, path, hash, format, and optional embedded data.

File Roles (MnaFileRole)

RoleDescription
RawRaw MEG/EEG recording
ForwardForward solution
InverseInverse operator
CovarianceNoise or data covariance matrix
SourceEstimateSource-level time series (.stc, .w)
BemBEM model geometry
SurfaceFreeSurfer surface mesh
AnnotationFreeSurfer cortical parcellation
DigitizerHead digitization points
TransformCoordinate transformation (head → MRI)
SourceSpaceSource space definition
EvokedAveraged evoked response
CustomUser-defined

Computational Graph

The heart of MNA is the computational graph: a directed acyclic graph (DAG) of processing nodes that transforms raw data into results.

MnaGraph

MnaGraph is the top-level graph container. It holds:

  • A list of MnaNode objects (the processing steps)
  • Graph-level input and output ports (graphInputs, graphOutputs)
  • A hierarchical parameter tree (MnaParamTree)
  • Connection and validation methods
MnaGraph graph;
graph.addNode(filterNode);
graph.addNode(inverseNode);
graph.connect("filter_01", "filtered_raw",
"inverse_01", "raw_input");

QStringList errors;
if (!graph.validate(&errors)) {
for (const auto& e : errors)
qWarning() << e;
}

MnaNode

Each node represents a single processing step:

FieldTypeDescription
idQStringUnique identifier (e.g., "filter_01")
opTypeQStringOperation type looked up in MnaOpRegistry (e.g., "dsp.band_pass_filter")
attributesQVariantMapOperation parameters (e.g., {"low_freq": 1.0, "high_freq": 40.0})
inputsQList<MnaPort>Input ports with upstream connections
outputsQList<MnaPort>Output ports
execModeMnaNodeExecModeExecution mode: Batch, Stream, Ipc, or Script
verificationMnaVerificationPre/post-condition checks and provenance
dirtyboolWhether the node needs re-execution

MnaPort

Typed input/output slot on a graph node:

FieldTypeDescription
nameQStringPort name, unique within a node
dataKindMnaDataKindType of data flowing through (e.g., FiffRaw, Forward, SourceEstimate)
directionMnaPortDirInput or Output
sourceNodeIdQStringUpstream node (for input ports)
sourcePortNameQStringUpstream output port name
streamProtocolQStringFor real-time: "fiff-rt", "lsl", "tcp", "shm"
streamEndpointQStringProtocol-specific address
cachedResultPathQStringPath to cached result file
cachedResultHashQStringSHA-256 hash for cache invalidation

Data Kinds (MnaDataKind)

KindDescription
FiffRawRaw MEG/EEG data (FIFF format)
ForwardForward solution
InverseInverse operator
CovarianceNoise or data covariance matrix
SourceEstimateSource-level time series
EpochsEpoched data
EvokedAveraged evoked response
MatrixGeneric Eigen matrix (for intermediates)
VolumeMRI volume data
SurfaceSurface mesh (FreeSurfer)
BemBEM model
AnnotationFreeSurfer annotation/parcellation
LabelROI label
RealTimeStreamLive data channel (MNE Scan / LSL / FIFF-RT)
CustomUser-defined data kind

Node Execution Modes (MnaNodeExecMode)

ModeDescription
BatchRuns once on static file-based inputs (default)
StreamRuns continuously on real-time data (MNE Scan integration)
IpcDelegates to an external process via inter-process communication
ScriptInline code executed via an interpreter (Python, shell, R, MATLAB, Julia)

Operator Schemas

Every operation type is described by an operator schema (MnaOpSchema) that declares the expected inputs, outputs, and attributes. Schemas enable validation before execution: the graph can verify that all required ports are connected, data kinds are compatible, and mandatory attributes are set.

MnaOpSchema

FieldTypeDescription
opTypeQStringOperation type identifier (e.g., "inv.compute_mne")
versionQStringSchema version (e.g., "2.2.0")
bindingQString"internal", "cli", or "script"
categoryQString"io", "preprocessing", "source_estimation", etc.
descriptionQStringHuman-readable description
libraryQStringWhich library provides the implementation
inputPortsQList<MnaOpSchemaPort>Expected input port descriptors
outputPortsQList<MnaOpSchemaPort>Expected output port descriptors
attributesQList<MnaOpSchemaAttr>Expected attribute descriptors with types and defaults

MnaOpRegistry

The singleton MnaOpRegistry maps operation type strings to schemas and implementation functions:

auto& reg = MnaOpRegistry::instance();

// Register a schema
reg.registerOp(mySchema);

// Register an implementation
reg.registerOpFunc("custom.my_filter", [](const QVariantMap& inputs,
const QVariantMap& attrs) -> QVariantMap {
// Process inputs using attrs
return {{"filtered", result}};
});

// Check and invoke
if (reg.hasOp("dsp.band_pass_filter")) {
auto schema = reg.schema("dsp.band_pass_filter");
auto func = reg.opFunc("dsp.band_pass_filter");
}

MnaRegistryLoader

Loads operator schemas from declarative JSON registry manifest files:

MnaRegistryLoader::loadDirectory("resources/mna/", MnaOpRegistry::instance());

The loader supports a master mna-registry.json file plus drop-in files from a mna-registry.d/ subdirectory. Later files override earlier entries for the same operation type.

Parameters

MnaParamTree

A hierarchical parameter store where each parameter has a path ("nodeId/attrKey") and an optional dynamic binding:

MnaParamTree tree;
tree.setParam("filter_01/low_freq", 1.0);
tree.setParam("filter_01/high_freq", 40.0);
tree.setParam("inverse_01/snr", 3.0);

QVariant val = tree.param("inverse_01/snr"); // → 3.0

MnaParamBinding

Dynamic bindings link a target parameter to a formula that is re-evaluated when a trigger condition is met:

FieldTypeDescription
targetPathQStringParameter path to control (e.g., "inverse_01/lambda")
expressionQStringFormula string (e.g., "clamp(ref('noise_est_01/snr') * 0.1, 0.01, 1.0)")
triggerQString"on_change", "periodic", or "manual"
periodMsintEvaluation period for periodic triggers
dependenciesQStringListPaths this binding reads from
MnaParamBinding binding;
binding.targetPath = "inverse_01/lambda";
binding.expression = "1.0 / pow(ref('inverse_01/snr'), 2)";
binding.trigger = "on_change";
binding.dependencies = {"inverse_01/snr"};

tree.addBinding(binding);

Execution

MnaGraphExecutor

The executor traverses the graph in topological order and invokes the registered operation function for each node:

QVariantMap graphInputs;
graphInputs["raw_file"] = "/data/sample_audvis_raw.fif";

auto ctx = MnaGraphExecutor::execute(graph, graphInputs);

// Access results
QVariant stc = ctx.results["inverse_01::source_estimate"];

Execution Modes

ModeDescription
Full executionexecute() — runs all nodes in topological order
Incremental executionexecuteIncremental() — runs only dirty nodes and their downstream dependents
Single-node executionexecuteNode() — runs one node for testing/debugging

Progress Callback

MnaGraphExecutor::setProgressCallback(
[](const QString& nodeId, int current, int total) {
qDebug() << QString("[%1/%2] %3").arg(current).arg(total).arg(nodeId);
});

Stream Mode

When a node's execMode is Stream, the executor connects to the real-time data source specified by the input port's streamProtocol and streamEndpoint, then continuously processes incoming data buffers. This integrates MNA pipelines with MNE Scan for real-time acquisition and processing.

Verification and Provenance

Each node carries a MnaVerification block containing:

  • Checks (MnaVerificationCheck) — declarative pre- and post-conditions evaluated by the executor
  • Results (MnaVerificationResult) — the outcome of each check after execution
  • Provenance (MnaProvenance) — a snapshot of the execution environment

Verification Checks

FieldTypeDescription
idQStringUnique check ID within the node (e.g., "cov_posdef")
descriptionQStringHuman-readable description
phaseQString"pre" (before execution) or "post" (after execution)
expressionQStringSimple evaluable expression (e.g., "rank(covariance) > 0")
scriptMnaScriptOptional script for complex checks (exit code 0 = pass)
severityQString"error" (abort), "warning" (log + continue), "info" (always continue)
onFailQStringRemediation hint shown on failure

MnaScript

Inline code that can be embedded in nodes (for Script exec mode) or verification checks:

FieldTypeDescription
languageQString"python", "shell", "r", "matlab", "octave", "julia"
interpreterQStringInterpreter command (e.g., "python3"); empty = auto-detect
interpreterArgsQStringListExtra args before the script file
codeQStringThe inline source code
sourceUriQStringOptional authoring-time reference
codeSha256QStringSHA-256 of code for integrity verification

Serialization Formats

MNA — JSON Text Format

The .mna format serializes the project as indented UTF-8 JSON. Every MNA class implements toJson() / fromJson():

QJsonObject json = project.toJson();
MnaProject proj = MnaProject::fromJson(json);

Advantages:

  • Human-readable — open in any text editor
  • Diff-friendly — track changes in Git line-by-line
  • Debuggable — inspect parameters and file references at a glance

Embedded data is Base64-encoded in the "data" field of an MnaFileRef:

{
"role": "Event",
"path": "events.fif",
"embedded": true,
"data": "AAAB..."
}

MNX — CBOR Binary Format

The .mnx format is the compact binary counterpart, using CBOR (RFC 8949) with a 4-byte magic header. It is the recommended format for distributing self-contained projects because embedded file data is stored as raw bytes (no Base64 overhead).

Wire format

Offset  Size  Content
────── ──── ───────────────────────────────────
0 4 Magic bytes: 0x4D 0x4E 0x58 0x31 ("MNX1")
4 n CBOR-encoded QCborMap (the MnaProject)

The magic header allows quick file-type detection without parsing the CBOR payload:

// Quick check
QFile f("pipeline.mnx");
f.open(QIODevice::ReadOnly);
QByteArray magic = f.read(4);
bool isMnx = (magic == QByteArrayLiteral("MNX1"));

Every MNA class implements toCbor() / fromCbor():

QCborMap cbor = project.toCbor();
MnaProject proj = MnaProject::fromCbor(cbor);

Advantages:

  • Compact — typically 30–60 % smaller than JSON
  • Fast parsing — CBOR is a binary format with no tokenization overhead
  • Native binary data — embedded files stored as CBOR byte strings (no Base64 expansion)
  • Self-contained — a single .mnx file can bundle all data files for a complete analysis

Embedded data is stored as raw CBOR byte strings — no Base64 expansion:

// JSON serialization (Base64)
json["data"] = QString::fromLatin1(data.toBase64());

// CBOR serialization (raw bytes, ~33 % smaller for binary payloads)
cbor["data"] = QCborValue(data); // native ByteArray

CBOR key reference

All CBOR map keys are identical to their JSON counterparts — no abbreviation or integer tags are used. This simplifies debugging and cross-language implementations.

MnaProject

KeyCBOR TypeNotes
mna_versionTextSchema version, e.g. "1.0"
nameText
descriptionText
createdTextISO 8601 with milliseconds
modifiedTextISO 8601 with milliseconds
subjectsArrayArray of MnaSubject maps
pipelineArrayArray of MnaNode maps

MnaSubject / MnaSession / MnaRecording

KeyCBOR TypeNotes
idTextUnique identifier
freesurfer_dirTextMnaSubject only
sessionsArrayMnaSubject only — array of MnaSession
recordingsArrayMnaSession only — array of MnaRecording
filesArrayMnaRecording only — array of MnaFileRef

MnaFileRef

KeyCBOR TypeNotes
roleTextFile role as string ("Raw", "Forward", …)
pathTextRelative POSIX path
sha256TextHex-encoded SHA-256
formatText"fiff", "stc", "mgh", …
size_bytesIntegerFile size in bytes
embeddedBooleanWhether data is inline
dataByteArrayRaw bytes (only when embedded == true)

MnaNode

KeyCBOR TypeNotes
idTextUnique node identifier
op_typeTextOperation type, e.g. "dsp.band_pass_filter"
exec_modeText"batch", "stream", "ipc", "script"
attributesMapNested CBOR map of operation parameters
inputsArrayArray of MnaPort maps
outputsArrayArray of MnaPort maps
ipc_commandTextExternal command (IPC mode)
ipc_argsArrayCommand-line arguments
ipc_work_dirTextWorking directory
ipc_transportText"stdio", "tcp", "shm", "file"
scriptMapMnaScript (Script mode)
verificationMapMnaVerification structure
tool_versionTextVersion string
executed_atTextISO 8601 timestamp
dirtyBooleanNeeds re-execution

MnaPort

KeyCBOR TypeNotes
nameTextPort name (unique within node)
data_kindText"fiff_raw", "forward", "inverse", …
directionText"input" or "output"
source_nodeTextUpstream node ID
source_portTextUpstream port name
stream_protocolText"fiff-rt", "lsl", "ftbuffer", "shm"
stream_endpointTextProtocol-specific address
stream_buffer_msIntegerRing-buffer length in milliseconds
cached_resultTextPath to cached output
cached_result_hashTextSHA-256 for cache validation

MnaScript

KeyCBOR TypeNotes
languageText"python", "shell", "r", "matlab", "octave", "julia"
interpreterTextInterpreter command (e.g. "python3")
interpreter_argsArrayExtra args before script file
codeTextInline source code
source_uriTextAuthoring-time reference
code_sha256TextSHA-256 of code
keep_temp_fileBooleanPreserve temp script after execution

Data type mapping

C++ / Qt TypeJSON EncodingCBOR Encoding
QStringJSON stringCBOR text string
QDateTimeISO 8601 string (Qt::ISODateWithMs)CBOR text string
QByteArray (data)Base64-encoded JSON stringCBOR byte string (major type 2)
int / qint64JSON numberCBOR integer
boolJSON true/falseCBOR true/false
QVariantQJsonValue::fromVariant()QCborValue::fromVariant()
QVariantMapQJsonObjectQCborMap via fromJsonObject()
note

MnaParamTree is serialized in the JSON format only. It is omitted from the CBOR representation; parameter bindings are handled at the application layer rather than persisted in .mnx files.

Size comparison example

Content.mna (JSON).mnx (CBOR)Savings
Metadata only (5 nodes, no data)4.2 KB1.8 KB57 %
With 10 MB embedded raw file13.4 MB10.1 MB25 %

Reading and Writing

MnaIO automatically selects the format based on the file extension:

#include <mna/mna_io.h>

// Read (dispatches by extension: .mna → JSON, .mnx → CBOR)
MnaProject project = MnaIO::read("pipeline.mnx");

// Write JSON
MnaIO::write(project, "pipeline.mna");

// Write CBOR binary
MnaIO::write(project, "pipeline.mnx");

Round-Trip Fidelity

Both formats guarantee lossless round-trip serialization. Every MNA struct carries an extras field (QJsonObject) that captures any JSON/CBOR keys not recognized by the current software version. On serialization, these unknown keys are written back. This means:

  • A project created by a newer MNA version (with additional fields) can be opened and re-saved by an older version without losing the new fields
  • Cross-application round-trips preserve foreign data: mne_browse, mne_inspect, and mne_scan each only modify the fields they own, leaving everything else intact (enriching mode)
Original file    →  fromJson()  →  MnaProject  →  toJson()  →  Saved file
(extras bag)
All unknown keys ─────────────────────────────────────────────→ All unknown keys preserved

Converting Between Formats

// JSON → Binary
MnaProject proj = MnaIO::read("pipeline.mna");
MnaIO::write(proj, "pipeline.mnx");

// Binary → JSON
MnaProject proj = MnaIO::read("pipeline.mnx");
MnaIO::write(proj, "pipeline.mna");

Or use the CLI:

mne_show_mna pipeline.mnx                 # inspect contents
mne_show_mna pipeline.mna --convert mnx # convert to binary (planned)

Example: Minimum-Norm Pipeline

{
"mnaVersion": "1.0",
"name": "audvis_dspm",
"description": "dSPM source localization of auditory/visual paradigm",
"created": "2026-04-19T10:00:00Z",
"subjects": [
{
"id": "sample",
"freeSurferDir": "subjects/sample",
"sessions": [
{
"id": "01",
"recordings": [
{
"id": "audvis",
"files": [
{ "role": "Raw", "path": "data/sample_audvis_raw.fif", "format": "fiff" },
{ "role": "Forward", "path": "data/sample_audvis-fwd.fif", "format": "fiff" },
{ "role": "Covariance", "path": "data/sample_audvis-cov.fif", "format": "fiff" }
]
}
]
}
]
}
],
"pipeline": [
{
"id": "read_raw_01",
"opType": "io.read_raw_fif",
"attributes": { "path": "data/sample_audvis_raw.fif" },
"inputs": [],
"outputs": [
{ "name": "raw", "dataKind": "FiffRaw", "direction": "Output" }
]
},
{
"id": "filter_01",
"opType": "dsp.band_pass_filter",
"attributes": { "low_freq": 1.0, "high_freq": 40.0 },
"inputs": [
{ "name": "raw_input", "dataKind": "FiffRaw", "direction": "Input",
"sourceNodeId": "read_raw_01", "sourcePortName": "raw" }
],
"outputs": [
{ "name": "filtered_raw", "dataKind": "FiffRaw", "direction": "Output" }
]
},
{
"id": "read_fwd_01",
"opType": "io.read_forward",
"attributes": { "path": "data/sample_audvis-fwd.fif" },
"inputs": [],
"outputs": [
{ "name": "forward", "dataKind": "Forward", "direction": "Output" }
]
},
{
"id": "read_cov_01",
"opType": "io.read_covariance",
"attributes": { "path": "data/sample_audvis-cov.fif" },
"inputs": [],
"outputs": [
{ "name": "covariance", "dataKind": "Covariance", "direction": "Output" }
]
},
{
"id": "inverse_01",
"opType": "inv.compute_mne",
"attributes": { "method": "dSPM", "snr": 3.0 },
"inputs": [
{ "name": "raw_input", "dataKind": "FiffRaw", "direction": "Input",
"sourceNodeId": "filter_01", "sourcePortName": "filtered_raw" },
{ "name": "forward", "dataKind": "Forward", "direction": "Input",
"sourceNodeId": "read_fwd_01", "sourcePortName": "forward" },
{ "name": "covariance", "dataKind": "Covariance", "direction": "Input",
"sourceNodeId": "read_cov_01", "sourcePortName": "covariance" }
],
"outputs": [
{ "name": "source_estimate", "dataKind": "SourceEstimate", "direction": "Output" }
],
"verification": {
"checks": [
{
"id": "cov_posdef",
"description": "Covariance matrix must be positive-definite",
"phase": "pre",
"expression": "rank(covariance) > 0",
"severity": "error"
}
]
}
}
]
}

CLI Tools

MNE-CPP ships several command-line tools for working with MNA files:

ToolDescription
mne_show_mnaPretty-print an .mna/.mnx file: project metadata, file inventory, and pipeline graph summary.
mne_inverse_pipelineBuild and execute a complete inverse pipeline from command-line arguments, saving the result as an .mna project.
mne_mna_bids_converterScan a BIDS dataset directory and generate an .mna project with file references and subject/session structure.

BIDS Integration

The mne_mna_bids_converter tool scans a BIDS-compliant dataset and generates an MNA project that mirrors the BIDS hierarchy:

mne_mna_bids_converter --bids-root /data/ds000117 --output ds000117.mna

The generated project contains MnaSubjectMnaSessionMnaRecordingMnaFileRef entries for all raw data files, with roles inferred from BIDS suffixes (_meg.fifRaw, _T1w.nii.gzVolume, etc.).

See Also