Python API reference¶
Auto-generated from the source docstrings. For a task-oriented walkthrough see the Python guide.
The native API:
mef3io.Reader ¶
Reader(path: str, password: str = '', backend: str = 'cpp', n_threads: int = 0, cache=None, warn_declarations: bool = True)
Read-only interface to a MEF 3.0 session.
Times throughout are uUTC (microseconds since the Unix epoch). Windowed reads fetch only the bytes they need, so they stay cheap on huge sessions.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
path
|
str
|
Path to the |
required |
password
|
str
|
Password for encrypted sessions. A level-2 password unlocks everything; a level-1 password reads the signal and technical metadata but leaves subject metadata locked. Empty for unencrypted sessions. |
''
|
backend
|
('cpp', 'pure')
|
Which implementation to use. Defaults to the C++ backend; the pure backend is not yet implemented. |
"cpp"
|
n_threads
|
int
|
Worker threads for RED block decoding. |
0
|
cache
|
str or None
|
Opt-in warm-start cache for channel metadata. |
None
|
warn_declarations
|
bool
|
Emit a :class: |
True
|
Examples:
>>> with mef3io.Reader("session.mefd") as r:
... x = r.read(r.channels[0], t0, t1) # float64, NaN in gaps
declaration_issues
property
¶
declaration_issues: list
Section-2 size declarations this session leaves unset.
The structured form of the :class:SessionDeclarationWarning raised on
open: a list of {"channel", "segment", "field"} dicts, empty when
the session declares everything. Free — computed at open from metadata
that was already parsed. It sees only what is missing; use
:class:mef3io.Validator to find what is merely wrong.
channels
property
¶
channels: list[str]
Channel names in the session, sorted.
Returns:
| Type | Description |
|---|---|
list of str
|
|
metadata
property
¶
metadata
Session subject/acquisition metadata as a :class:mef3io.Metadata.
Built from the first channel (metadata is session-wide). Subject fields are empty unless the reader was opened with a level-2 password.
Returns:
| Type | Description |
|---|---|
Metadata
|
|
close ¶
close() -> None
Release the backend and its file handles.
After this the reader is CLOSED: a further read raises rather than quietly reopening the session. A caller who closed to release handles — before archiving the directory, or to stay under an fd limit — got them back without notice, which is the opposite of what they asked for. Closing twice is harmless.
info ¶
info(channel: str) -> dict
Channel metadata.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
channel
|
str
|
Channel name. |
required |
Returns:
| Type | Description |
|---|---|
dict
|
Keys: |
read ¶
read(channel: str, t0: Optional[int] = None, t1: Optional[int] = None, n_threads: Optional[int] = None) -> np.ndarray
Read float64 samples on the uniform sampling grid.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
channel
|
str
|
Channel name. |
required |
t0
|
int or float
|
Half-open time window |
None
|
t1
|
int or float
|
Half-open time window |
None
|
n_threads
|
int
|
Per-call override of the reader's thread count ( |
None
|
Returns:
| Type | Description |
|---|---|
ndarray
|
1-D float64 array on the sampling grid. Discontinuity gaps are
filled with |
See Also
read_raw : the unscaled int32 form with an explicit validity mask.
read_raw ¶
read_raw(channel: str, t0: Optional[int] = None, t1: Optional[int] = None, n_threads: Optional[int] = None) -> dict
Read the stored int32 counts with an explicit validity mask.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
channel
|
str
|
Channel name. |
required |
t0
|
int or float
|
Half-open |
None
|
t1
|
int or float
|
Half-open |
None
|
n_threads
|
int
|
Per-call thread-count override (see :meth: |
None
|
Returns:
| Type | Description |
|---|---|
dict
|
Keys: |
segments ¶
segments(channel: str) -> list[dict]
Per-segment map of a channel — what data is where.
Read from metadata only (nothing is decoded), so it is cheap even for
huge, gap-riddled sessions. Use it to locate data across large
recording gaps, then :meth:toc for the block-level view within a
segment.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
channel
|
str
|
Channel name. |
required |
Returns:
| Type | Description |
|---|---|
list of dict
|
One dict per segment (sorted by segment number) with keys
|
toc ¶
toc(channel: str) -> list[dict]
Block-level table of contents, for seeking and viewers.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
channel
|
str
|
Channel name. |
required |
Returns:
| Type | Description |
|---|---|
list of dict
|
One dict per RED block with |
records ¶
records(channel: Optional[str] = None) -> list[dict]
Read records (annotations).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
channel
|
str or None
|
Channel name for channel-level records, or |
None
|
Returns:
| Type | Description |
|---|---|
list of dict
|
One dict per record with |
mef3io.Writer ¶
Writer(path: str, overwrite: bool = False, password1: str = '', password2: str = '', units: Optional[str] = None, block_length: Optional[int] = None, n_threads: int = 0, metadata=None, durability: str = 'fast')
Write a MEF 3.0 session.
Times throughout are uUTC (microseconds since the Unix epoch). The
first write to a channel creates segment 0; later writes append in-segment
(extending the existing files) unless new_segment=True.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
path
|
str
|
Path to the |
required |
overwrite
|
bool
|
|
False
|
password1
|
str
|
Level-1 and level-2 passwords. MEF has no "level-1 only" files, so to encrypt a session pass both; leave both empty for no encryption. |
''
|
password2
|
str
|
Level-1 and level-2 passwords. MEF has no "level-1 only" files, so to encrypt a session pass both; leave both empty for no encryption. |
''
|
units
|
str or None
|
Physical units label stored in metadata (e.g. |
None
|
block_length
|
int or None
|
RED block size in samples. |
None
|
n_threads
|
int
|
Worker threads for RED encoding ( |
0
|
metadata
|
Metadata or dict
|
Session-wide subject/acquisition metadata written to every channel.
Also settable later via :meth: |
None
|
durability
|
('fast', 'full')
|
How hard an append works to survive an unclean shutdown.
What that costs: the order in which the
A fresh write is unaffected either way — there is nothing underneath it to lose. |
"fast"
|
Examples:
>>> with mef3io.Writer("session.mefd", overwrite=True, units="uV") as w:
... w.write("ch1", data, start_uutc, fs=256.0) # NaN marks gaps
set_metadata ¶
set_metadata(metadata) -> None
Set session-wide subject/acquisition metadata (a
:class:mef3io.Metadata, or a flat dict of fields). Call before
writing; applies to every channel.
close ¶
close() -> None
Finalize and release the writer. Called automatically on context-manager exit.
write ¶
write(channel: str, data: ndarray, start_uutc: int, fs: float, precision: int = -1, new_segment: bool = False) -> dict
Write float data; NaN runs become discontinuity gaps.
Values are quantized to int32 counts as round(data * 10**precision)
with the conversion factor 10**-precision kept in metadata. NaN
samples are not stored — they read back as NaN.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
channel
|
str
|
Channel name (created on first write). |
required |
data
|
array_like
|
Any numeric input; coerced to float64 (lists, int arrays,
|
required |
start_uutc
|
int or float
|
Timestamp of the first sample, uUTC. |
required |
fs
|
float
|
Sampling frequency in Hz. |
required |
precision
|
int
|
Decimal precision for quantization. |
-1
|
new_segment
|
bool
|
Force a fresh segment instead of appending in-segment. |
False
|
Returns:
| Type | Description |
|---|---|
dict
|
|
Raises:
| Type | Description |
|---|---|
RuntimeError
|
On an append conflict (fs / conversion-factor mismatch, or data starting before the segment's end). |
write_int32 ¶
write_int32(channel: str, data: ndarray, ufact: float, start_uutc: int, fs: float, valid: Optional[ndarray] = None, new_segment: bool = False) -> dict
Write integer counts verbatim with a conversion factor (bit-exact).
The primitive path: counts are stored exactly as given, with ufact
(e.g. an amplifier's volts-per-bit) in metadata. Physical units on read
are counts * ufact.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
channel
|
str
|
Channel name (created on first write). |
required |
data
|
array_like of int
|
Integer counts (any integer width). Stored bit-exact. |
required |
ufact
|
float
|
Conversion factor from counts to physical units. |
required |
start_uutc
|
int or float
|
Timestamp of the first sample, uUTC. |
required |
fs
|
float
|
Sampling frequency in Hz. |
required |
valid
|
array_like or None
|
Same length as |
None
|
new_segment
|
bool
|
Force a fresh segment instead of appending in-segment. |
False
|
Returns:
| Type | Description |
|---|---|
dict
|
|
Raises:
| Type | Description |
|---|---|
TypeError
|
If |
ValueError
|
If any value is outside the int32 range (it would wrap). |
RuntimeError
|
On an append conflict. |
write_annotations ¶
write_annotations(annotations, channel: Optional[str] = None) -> None
Write records (annotations).
Replaces the records at the given level. In encrypted sessions record bodies are level-2 encrypted.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
annotations
|
iterable of dict or pandas.DataFrame
|
Each record needs |
required |
channel
|
str or None
|
Channel name for channel-level records, or |
None
|
mef3io.archive_session ¶
archive_session(session_path, tar_path: Optional[str] = None, overwrite: bool = False) -> str
Pack a session directory into a single uncompressed tar archive.
The archive (conventionally name.mefd.tar) is a plain ustar file:
:class:Reader opens it directly — no extraction — and any tar tool
(tar -xf) reproduces the original directory. Because it is
uncompressed, windowed reads still fetch only the byte ranges they need.
The source directory is left untouched; output is deterministic, so
archiving the same session twice yields identical bytes. Tar sessions are
read-only: :class:Writer refuses .tar paths.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
session_path
|
str or path - like
|
The |
required |
tar_path
|
str
|
Target archive path; must end |
None
|
overwrite
|
bool
|
Replace an existing target archive. Default |
False
|
Returns:
| Type | Description |
|---|---|
str
|
Path of the created archive. |
Examples:
>>> tar = mef3io.archive_session("session.mefd")
>>> with mef3io.Reader(tar) as r:
... x = r.read(r.channels[0])
mef3io.extract_session ¶
extract_session(tar_path, dest_dir: Optional[str] = None, overwrite: bool = False) -> str
Unpack a session archive back into a .mefd directory.
The inverse of :func:archive_session — after extraction the directory is
a normal writable session again. The session root inside the archive is
stripped, so dest_dir becomes the session directory itself; archives
from foreign tar tools work too. A failed extraction never leaves a
half-written directory behind.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
tar_path
|
str or path - like
|
The session archive to unpack; must end |
required |
dest_dir
|
str
|
Target directory; must end |
None
|
overwrite
|
bool
|
Replace an existing target directory. Default |
False
|
Returns:
| Type | Description |
|---|---|
str
|
Path of the extracted session directory. |
Examples:
>>> session = mef3io.extract_session("session.mefd.tar")
>>> with mef3io.Writer(session) as w: # writable again
... w.write("ch1", more_data, t, fs=256.0)
Validation and repair¶
See the validation guide for what each check means.
mef3io.Validator ¶
Validator(path: str, password: str = '', channels: Sequence[str] | None = None, segments: Sequence[int] | None = None, exact_difference_bytes: bool = True)
Check one MEF 3.0 session. Read-only: it cannot write a byte.
There is no repair method here by design — see :func:repair_session.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
path
|
str
|
A |
required |
password
|
str
|
Needed to read section 2 of an encrypted session. |
''
|
channels
|
sequence of str
|
Restrict to these channels. Default: every channel. |
None
|
segments
|
sequence of int
|
Restrict to these segment numbers. Default: every segment. |
None
|
exact_difference_bytes
|
bool
|
Read every RED block header to learn the real
|
True
|
available_checks
staticmethod
¶
available_checks() -> list[Check]
Every check in the registry, in the order they run.
describe_check
staticmethod
¶
describe_check(check_id: str) -> Check | None
One check by id, or None if unknown.
validate ¶
validate(check_ids: Iterable[str] | None = None) -> Report
Run every check (or only check_ids) and return the full report.
Never modifies the session.
mef3io.Report
dataclass
¶
Report(findings: tuple[Finding, ...] = (), skipped: tuple[SkippedSegment, ...] = (), segments_checked: int = 0, segments_repaired: int = 0, checks_run: tuple[str, ...] = (), checks_repaired: tuple[str, ...] = (), path: str = '', channels: tuple[str, ...] = (), segments: tuple[int, ...] = (), measured_difference_bytes: bool = True)
The result of a validate or repair pass.
ok
property
¶
ok: bool
True when nothing worse than a warning is left outstanding.
Findings this pass repaired no longer count against it, so a repair
that resolved everything reports ok. A skipped segment always
does count — an unchecked segment is not a clean one.
unresolved
property
¶
unresolved: tuple[Finding, ...]
Findings that are still outstanding — nothing repaired this pass.
repairable_check_ids
property
¶
repairable_check_ids: list[str]
Distinct check ids that reported something a repair could fix.
Pass these to :func:repair_session to opt in deliberately.
scope_caveat ¶
scope_caveat() -> str
One sentence naming what this run did not cover, or "" if it covered everything.
summary ¶
summary(max_examples: int = 3) -> str
A full human-readable report: counts, then each check that fired.
mef3io.Finding
dataclass
¶
Finding(check_id: str, severity: str, channel: str, segment: int, path: str, field: str, stored: str, expected: str, message: str, repairable: bool, repaired: bool)
One problem, found by one check, in one segment.
mef3io.Check
dataclass
¶
Check(id: str, title: str, description: str, severity: str, repairable: bool)
One check in the registry.
mef3io.available_checks ¶
available_checks() -> list[Check]
Every check, in the order they run. Ids are stable API.
mef3io.validate_session ¶
validate_session(path: str, **kwargs) -> Report
Check a session and return the report. Reads only.
Accepts the same keyword arguments as :class:Validator.
mef3io.repair_session ¶
repair_session(path: str, check_ids: Sequence[str], backup: bool = True, **kwargs) -> Report
Rewrite the declarations named in check_ids — and only those.
This is the only function in mef3io that modifies an existing session, and
it is deliberately not a method of :class:Validator: inspecting a session
and rewriting one should not be reachable through the same object.
Every check still runs, so the returned report describes the whole session;
Finding.repaired marks what was actually written — a repair that
declines to change anything is not counted.
Only declarations are rewritten: metadata section 2 and the universal
headers of .tmet/.tidx/.tdat. Sample data and the block index
are never touched. A segment whose CRCs do not verify is reported and left
alone, and a .mefd.tar archive is refused outright (extract it first).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
path
|
str
|
A |
required |
check_ids
|
sequence of str
|
Which repairs to apply. Required and non-empty — repairs are never
implicit. :attr: |
required |
backup
|
bool
|
Copy each file before rewriting it, into |
True
|
**kwargs
|
The same keyword arguments as :class: |
{}
|
Raises:
| Type | Description |
|---|---|
ValueError
|
If |
mef3io.recover_session ¶
recover_session(path: str, apply: bool = False, backup: bool = True, password: str = '') -> RecoveryReport
Make each segment's block index and data agree after an interrupted write.
This is not :func:repair_session, which only ever rewrites
declarations and is safe to run on anything. Recovery may truncate the
index, rebuild index entries from the data file, and drop a trailing
fragment — so it is a dry run unless you ask for it, and it backs up what it
changes first.
An interrupted append leaves one of two shapes, and they are handled differently because one has lost data and the other has not:
- index ahead of data — entries reference bytes that never landed. Those samples do not exist, so the entries are dropped.
- data ahead of index — blocks reached
.tdatbut the index was not extended. Those samples do exist, so they are recovered: a RED block header carries the sample count, byte count, start time and discontinuity flag, which is everything an index entry needs. Only blocks whose CRC verifies are indexed; a torn tail is not a block.
With the default durability="full" an append cannot leave either state.
They are reachable with durability="fast", which is what makes that
trade a reasonable one to offer.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
path
|
str
|
|
required |
apply
|
bool
|
False reports what it would do and writes nothing. |
False
|
backup
|
bool
|
Save what changes to |
True
|
password
|
str
|
Only needed for encrypted sessions. |
''
|
Returns:
| Type | Description |
|---|---|
RecoveryReport
|
|
Notes
Declarations are not updated here. Run :func:repair_session afterwards —
python -m mef3io recover does it for you.
mef3io.RecoveryReport
dataclass
¶
RecoveryReport(segments: tuple[RecoveredSegment, ...] = (), skipped: tuple[str, ...] = (), segments_examined: int = 0, applied: bool = False, backup_root: str = '')
What recover_session found, and did or would do.
mef3io.RecoveredSegment
dataclass
¶
RecoveredSegment(channel: str = '', segment: int = 0, path: str = '', blocks_before: int = 0, blocks_after: int = 0, blocks_recovered: int = 0, blocks_dropped: int = 0, tdat_bytes_dropped: int = 0, action: str = '')
One segment that an interrupted write left inconsistent.
mef3io.SessionDeclarationWarning ¶
Bases: UserWarning
A session leaves size declarations unset that other readers rely on.
Raised on open, once per process per session (Python's default warning filter suppresses a repeat of the same message from the same line). It never affects mef3io's own reads — mef3io sizes every buffer from the block headers themselves — but meflib-based readers (CyberPSG and most established MEF tooling) allocate from metadata section 2 before decoding, and cannot tell an unset field from a real measurement.
Silence it like any warning::
warnings.filterwarnings("ignore", category=mef3io.SessionDeclarationWarning)
or per call with Reader(path, warn_declarations=False). To see the
detail, or to fix it, use :class:mef3io.Validator.
Metadata objects¶
mef3io.Metadata
dataclass
¶
Metadata(subject: Subject = Subject(), acquisition: Acquisition = Acquisition())
Session-wide metadata: a :class:Subject and an :class:Acquisition
block. Written to every channel; on read, reflects the first channel.
mef3io.Subject
dataclass
¶
Subject(name_1: str = '', name_2: str = '', id: str = '', recording_location: str = '', gmt_offset: int = 0)
Subject / recording-context metadata (MEF section 3, level-2 encrypted).
mef3io.Acquisition
dataclass
¶
Acquisition(session_description: str = '', channel_description: str = '', reference_description: str = '', acquisition_channel_number: int = 1, low_frequency_filter: float = _UNSET_HZ, high_frequency_filter: float = _UNSET_HZ, notch_filter: float = _UNSET_HZ, line_frequency: float = _UNSET_HZ)
Descriptive / acquisition metadata (MEF section 2, level-1 encrypted).
Filter settings default to -1.0 meaning "not recorded".
Legacy mef_tools compatibility¶
Drop-in replacements for mef_tools.io — from mef3io import MefReader,
MefWriter. Same call shapes and defaults as the legacy classes; see the
legacy comparison for the measured differences.
mef3io.compat.MefReader ¶
MefReader(session_path: str, password2: Optional[str] = None, warn_declarations: bool = True)
mef_tools.io.MefReader-compatible reader.
session_path may also be an uncompressed tar archive of a session
(name.mefd.tar, see :func:mef3io.archive_session).
mef3io.compat.MefWriter ¶
MefWriter(session_path, overwrite=False, password1=None, password2=None, verbose=False, metadata=None, durability='fast')
mef_tools.io.MefWriter-compatible writer.
Like the legacy writer, appends extend the channel's last segment in place
(in-segment append); pass new_segment=True to start a fresh segment.
Metadata works two ways: mutate the legacy section3_dict /
section2_ts_dict (e.g. w.section3_dict['subject_ID'] = 'Smith') as
with mef_tools, or use the modern object via set_metadata / the
metadata= argument (mef3io.Metadata).
mef_block_len
property
writable
¶
mef_block_len
RED block length in samples (None = derive from fs, like legacy).
max_nans_written
property
writable
¶
max_nans_written: int
Kept for legacy compatibility. mef3io always splits data on NaN runs (never stores NaN as values), i.e. it behaves like the legacy writer's recommended setting of 0; other values are accepted and ignored.
record_offset
property
writable
¶
record_offset: int
Kept for legacy compatibility. mef3io writes records with a zero recording-time offset (annotation times round-trip unchanged either way); non-zero values are accepted and ignored.
set_metadata ¶
set_metadata(metadata) -> None
Set session-wide subject/acquisition metadata (a
:class:mef3io.Metadata or a flat dict). Applied on the next write.
get_mefblock_len ¶
get_mefblock_len(fs: float) -> int
Block length that will be used for data at fs (legacy formula).