Skip to content

Read a coregistration in Python

You have a saved result (pair.coreg.json) and the two recordings it was made on. Every time below, passed in or returned, is reference-clock uUTC (microseconds since the Unix epoch, on the reference recording's clock), whichever file you read.

One file, like a mef3io.Reader

from mef3io_coregistration.reader import CoregisteredSession

with CoregisteredSession("pair.coreg.json", file="B") as r:   # "A", "B", "reference" or "moving"
    r.channels                        # this file's channels
    x = r.read("C3", t0, t1)          # float64 over [t0, t1); NaN in gaps and outside the file
    y = r.read("C3-C4", t0, t1)       # bipolar: subtracted on the file's own samples, then resampled
    ts = r.times("C3", t0, t1)        # the reference-clock time of each sample in x
    r.info("C3")                      # metadata; start_time/end_time on the reference clock
    r.records()                       # annotations, their times on the reference clock

The reference file comes back exactly as mef3io returns it. The moving file is resampled through the clock map onto a uniform grid on the reference clock, at its own sampling rate (fs= for another); only the window you ask for is read, and consecutive reads tile without overlap or loss. A channel can be a name, an "anode-cathode" string or an (anode, cathode) tuple.

Code written for the legacy mef_tools reader needs only the constructor changed:

from mef3io_coregistration.reader import CoregisteredMefReader

mr = CoregisteredMefReader("pair.coreg.json", file="moving")   # was: MefReader(path)
x = mr.get_data(["C3", "C3-C4"], t0, t1)
fs = mr.get_property("fsamp", "C3")
mr.close()

Both files as one recording

from mef3io_coregistration.reader import CoregisteredRecording

with CoregisteredRecording.open("pair.coreg.json") as rec:
    rec.channels                                   # one logical channel per name; shared names linked
    rec.set_channel("C3", a="C3", b="C3", prefer="B")
    sig = rec.get_data("C3", t0, t1)
    sig.data, sig.ts, sig.fs                       # samples on the preferred file's grid
    sig.source                                     # per sample: 1 = file A, 2 = file B, 0 = no data
    sig.extrapolated                               # True where the map is held beyond its data
    for source, s0, s1 in sig.runs():              # single-file stretches: analyse each on its own
        ...

The preferred file serves the channel wherever it has data and the other file fills in where it has none. The two files may name and montage a channel differently (a="LA1", b=("EEG LA1-Ref", "Fz")). Filling is allowed only when the two sources agree over their overlap (rec.agreement("C3"): sign, gain, units); state how they relate with scale_a/offset_a/scale_b/ offset_b (scale_b=-1 inverts), or override with allow_mismatch=True. Methods ยง8 explains the check.

Event times between the clocks

t_in_b = rec.to_file_time(t_ref, "B")         # reference clock -> file B's own clock
t_ref = rec.to_reference_time(t_in_b, "B")    # and back

Sample-aligned arrays

CoregisteredReader reads the moving file onto exactly the reference file's sample grid, so the two arrays can be stacked or subtracted sample by sample:

from mef3io_coregistration.reader import CoregisteredReader

rd = CoregisteredReader.from_result("pair.coreg.json")
a = rd.get_data("C3", t0, t1, file="A")                    # native reference samples
b = rd.get_data("C3", t0, t1, file="B", coregister=True)   # resampled onto them
assert (a.ts == b.ts).all()
rd.close()

If the recordings are no longer where the result says, pass their paths: see Open a result from a pipeline. A longer walk through all of this is 02_read_coregistered_recording.py; the classes are in the API reference.