mef3io 1.4.0¶
Released 2026-10-16.
Highlights¶
- De-identified copies of a recording.
mef3io.anonymize_session(src, dst, shift_us)writes a copy of a session that can be shared: every date and time is moved by the same amount, the patient's identity is removed, and the signal is copied sample for sample. The original is never changed. - Times move everywhere they are stored, including the times inside
seizure, epoch and cursor annotations, so events still line up with the
signal. The time-zone fields are set to fixed values (recording-time
offset 0, GMT offset 0, no daylight-saving dates).
mef3io.anonymize.reference_shiftcomputes the shift from the date you want the copy to start on, keeping the UTC or the local time of day. - Identity is removed: subject names, ID and recording location, the session name, the descriptions, annotation text (dropped by default; or kept with only the names mef3io knows removed, or decided by your own function), file identifiers and other programs' private header areas. Keeping annotation text does not find dates or other names written in it.
- Channels can be left out (
exclude_channels=orchannels=), and nothing of a left-out channel stays in the copy. - Encryption: the copy is unencrypted unless you give it new passwords
(
output_password=); the source's passwords are never carried over. Data blocks the source had encrypted stay encrypted under the new passwords, or are decrypted only when the copy has none. - Checked before it is published. Every copy is compared with its
source by
mef3io.anonymize.verify(samples byte-identical, every time moved by exactly the shift, no original date or name left anywhere) before it gets its final name. A failure or a cancel leaves nothing behind. - Sessions using meflib's deprecated recording-time offset are accepted when their offset is unambiguous; the copy has offset 0.
- From a terminal:
python -m mef3io anonymize SRC DST --shift-us Nor--reference-date YYYY-MM-DD [--tz ZONE].
Python and C++ only, not MATLAB. Guide:
De-identification;
runnable example:
13_anonymize.py
(#70).
- Anonymizing from an application
(#111):
- mef3io.anonymize.check(src, dst, shift_us, ...) runs every check
anonymize_session makes before it writes, with the same arguments, and
returns all the refusals instead of raising the first. It writes nothing
and reads only metadata (never the data files), so a name, a channel
selection or a date can be checked while the user is still choosing it.
Both run one pre-flight function in the C++ core, so they cannot
disagree. shift_us=None leaves the date open.
- Every refusal has a stable code, for example dst-exists,
name-contains-identity, shift-below-floor, rto-disputed,
channel-unknown, id-number-time: as exc.code (with exc.details)
on the exception anonymize_session raises, and as Refusal.code in a
check. The full table is in
Refusal codes
and in mef3io.anonymize.REFUSAL_CODES. Match on the code, not the
message.
- AnonymizeReport.source_start_us, source_end_us and
source_channel_times hold the source's original times. They are the
key that gives the real dates back, so they are returned to the caller
only: never written into the copy, never logged or put in a message, and
left out of repr, summary(), pickles and (unless asked) to_dict().
No refusal or verification message names a source time.
- mef3io.password_interop_issue(password) says why a password will not
work as expected in other MEF readers (more than 15 characters, more
than 15 UTF-8 bytes, a character above U+00FF), or returns None. It is
the rule behind PasswordInteropWarning, so an application can warn
before it writes. The C++ core (mef3io::password_interop_issue) and
the C ABI (mef3io_password_interop_issue) already had it.
check is in Python and C++ (mef3io::check_anonymize,
mef3io::AnonymizeError); not in MATLAB.
- Opening a session no longer freezes the rest of a Python program.
Reader(...) scans the session and reads its metadata with the GIL
released, as reads and writes already did, so a GUI stays responsive while
a session opens on a worker thread, and several sessions can be opened at
once. Before, every other thread stopped for the whole open: about 0.6 s
on 64 channels x 60 segments, seconds on a long recording. toc,
segments, records and Writer.write_annotations release it too, and
so does opening a Writer on an existing session
(#79).
- Reads on one Reader can run from several threads at once, for
example in a data loader or a viewer (see Fixes): reads, toc,
segments and records. Until
#110: with cache=, make
one read (or toc / segments / records) before sharing the Reader,
because info, channels and metadata come from the snapshot and do not
open it; do not close() it while other
threads read; open-time or record warnings may repeat. A Writer is not
for sharing
(Parallelism).
- Warnings carry the session as data, not in their text. Every warning
mef3io emits is now a mef3io.MefWarning (still a UserWarning) with a
path attribute (the session, as you passed it) and details (the
structured items behind the message, such as the Reader.problems entries
or the block-copy mismatches). The message no longer starts with the path,
and the session's location and name inside it are replaced by
<session>. Only those are replaced: channel names and file names inside
the session can still appear, and they can be identifying.
warnings.filterwarnings("ignore", category=mef3io.MefWarning) silences
all of them. Python's "default" and "module" filter actions now show
each warning once per session
(Warnings carry the path as data,
#96).
- inspect() reports more.
SessionSummary.legacy_password_mapping is True when the password
opened some file only through the password mapping of mef3io 1.2.0 and
earlier (#84), with a legacy-password entry in warnings; as with every
other condition, inspect reports it as data and does not emit
LegacyPasswordWarning. ChannelSummary.channel_description holds each
channel's description; it can identify the subject, so, like the other
identity values, it is returned only with include_values=True, and it is
None when the part of the file holding it is locked
(#96).
- Provenance operation 6, anonymize, is now assigned: every file of a
de-identified copy carries it
(format reference).
Fixes¶
- Reading one session from several threads at once no longer crashes.
Sharing one
Readeracross threads could free a block index that another thread was still reading when the index cache evicted it, and the process aborted. Each call now holds its own reference to the index it reads, and the reader's shared state is locked. - A data race when decrypting encrypted metadata for the first time from two threads at once is gone: the decryption table is now fixed when mef3io is compiled instead of being filled on first use.
Upgrading¶
- Warning text changed. The messages of
SessionDeclarationWarning,UnreadableSegmentWarning,BlockCopyWarning,LegacyPasswordWarningandPasswordInteropWarningno longer start with the session path, and file paths inside them read<session>/.... A warning about record files or appended segments that used to name the first one no longer does (they are inw.message.details). Code that matched on the path in the text should usew.message.pathinstead. Filters by category are unaffected, and so is a filter on the rest of the text (for exampleignore:.*leaves size declarations unset.*). - New dataclass fields.
ChannelSummary.channel_descriptionandSessionSummary.legacy_password_mappingare new. Code that receives these frominspectneeds no change; code that builds them itself, by position, must add them. - Only if you used
anonymize_sessionfrom a development build (it is new in this release): with ambiguous session-level annotation files (several.rdatat the session root, none named after the session) it now raisesFormatError(coderecords-ambiguous), asinspectand theReaderdo, instead ofWriteConflictError. Both areMefErrors; catchMefErroror checkexc.code. A time in anEpocorCursID number is now refused before anything is written (id-number-time), and a channel that holds only annotations no longer fails the copy's verification. - Provenance readers may now meet operation code 6 (
anonymize) in files of a de-identified copy; it was reserved before. - From 1.2.x or earlier: see
1.3.0 for
extract_session(overwrite=True), non-ASCII passwords,Cancelledand strictinspect. - From 1.1.2 or earlier: sessions written by those versions declare some
buffer sizes as
0, which can crash meflib-based readers; see 1.1.3 and Validating and repairing.
Full list of changes: v1.3.0...v1.4.0.