MATLAB guide¶
The MATLAB binding wraps the same C++ core as the Python package — identical
semantics, identical performance (see the
benchmark), and
the same version number. It ships as a +mef3io package (two handle classes)
over a single compiled MEX.
Setup¶
Either download the mef3io-matlab-vX.Y.Z.zip bundle from a
GitHub release — classes and
the CI-tested MEX for all platforms in one ready-to-addpath folder (see
Install for the caveats) — or compile once from the repo
(~30 s, needs a C++20 compiler via mex -setup C++):
run('<repo>/matlab/build_mex.m')
addpath('<repo>/matlab')
Verify: mef3io_mex('version') prints the library version. Test suite:
run('<repo>/matlab/test_mef3io.m').
Times everywhere are uUTC (microseconds since the Unix epoch), int64 or
double. Errors from the core surface as MATLAB errors (id mef3io:error)
with the C++ message.
Writing¶
w = mef3io.Writer('session.mefd', Overwrite=true, Units='uV');
w.write('ch1', data, startUutc, 256); % NaN = gap, precision inferred
w.write('ch1', more, t2, 256); % appends in-segment
w.write('ch1', x, t3, 256, NewSegment=true); % force a new segment
w.writeInt32('ch2', counts, 0.01, startUutc, 256, Valid=mask);
w.writeAnnotations(struct('time', t, 'text', 'note'), 'ch1');
delete(w); % or let it go out of scope
mef3io.Writer(path, ...) name-value options: Overwrite (default false —
an existing session is reopened for appending), Password1/Password2
(both required for encryption), Units, BlockLength, Threads.
write(channel, data, startUutc, fs, Precision=-1, NewSegment=false)— float path: NaN runs become gaps;Precisionsets the conversion factor to10^-precision,-1infers it (or reuses the segment's on append). Returns a summary struct (samples_written,blocks,gaps_skipped,segment).writeInt32(channel, data, ufact, startUutc, fs, Valid=[], NewSegment=false)— verbatim int32 counts + conversion factor (bit-exact round trip).datamust be integer-typed: floating-point input errors (usewritefor float data) and values beyond the int32 range error instead of saturating.Validis any numeric/logical mask, nonzero = valid.writeAnnotations(records, channel)— struct array (or table) with fieldstime(required),type(default'Note'),text,duration. Omitchannelfor session-level records. Replaces records at that level.setMetadata(md)/ theMetadata=constructor option — session-wide subject/acquisition metadata. Use the object API (mirrors Python):
md = mef3io.Metadata( ...
subject=mef3io.Subject(id="MRN-123", name_1="Jane", recording_location="EMU-4"), ...
acquisition=mef3io.Acquisition(session_description="pre-surgical", line_frequency=60));
w = mef3io.Writer('s.mefd', Overwrite=true, Metadata=md);
Every field defaults, so set only what you have. setMetadata also accepts a
plain struct with the flat field names (subject_id, session_description,
…). Read it back with r.metadata, a mef3io.Metadata object with .subject
and .acquisition (subject fields empty without a level-2 password).
Reading¶
r = mef3io.Reader('session.mefd'); % password as 2nd argument if encrypted
r.channels % cellstr
info = r.info('ch1'); % fs, ufact, times, subject metadata
x = r.read('ch1'); % whole channel: double column, NaN gaps
x = r.read('ch1', t0, t1); % [t0, t1) window
raw = r.readRaw('ch2'); % int32 counts + logical valid mask
segs = r.segments('ch1'); % what data is where, per segment
blocks = r.toc('ch1'); % block-level index (struct of vectors)
anns = r.records('ch1'); % annotations; r.records() = session level
delete(r);
info returns sampling_frequency, units_conversion_factor,
units_description, start_time/end_time (int64 uUTC),
number_of_samples (stored samples; gaps not counted), n_segments,
section3_available and the subject metadata fields (empty under a level-1
password — the signal still reads correctly, unlike the legacy stack).
Tar archives (single-file sessions)¶
Pack a session into one uncompressed tar file and read it in place — handy for sharing and safe archiving:
tarPath = mef3io.archiveSession('session.mefd'); % -> 'session.mefd.tar'
r = mef3io.Reader(tarPath); % no extraction needed
Windowed reads stay cheap (the archive is uncompressed, so only the needed
byte ranges are fetched), and any tar tool restores the directory
(tar -xf session.mefd.tar). Tar sessions are read-only — mef3io.Writer
refuses .tar paths; unpack first to modify:
sessionDir = mef3io.extractSession(tarPath); % -> 'session.mefd' back
w = mef3io.Writer(sessionDir); % writable again
archiveSession(dir, tarPath, Overwrite=true) / extractSession(tarPath,
destDir, Overwrite=true) pick targets and replace existing ones; they mirror
Python's mef3io.archive_session / extract_session.
Session naming is enforced at every entry point: directories must end
.mefd, archives .mefd.tar — anything else is refused, so a stray
directory or file can never be misread (or overwritten) as a session.
How it is put together¶
matlab/mef3io_mex.cpp is a single command-dispatch MEX over the flat C ABI
(core/include/mef3io/c_api.h), so C++ exceptions
never cross the MEX boundary. The +mef3io classes manage the native
handles: delete closes them, and the MEX keeps itself locked while any
handle is alive, so clear mex cannot dangle a session.