def cut_traces(cntfile: FileName, annotation: Annotations) -> List[TraceData]: """cut the tracedate from a matfile given Annotations args ---- cntfile: FileName the cntfile for cutting the data. must correspond in name to the one specified in the annotation annotation: Annotations the annotations specifying e.g. onsets as well as pre and post durations returns ------- traces: List[TraceData] """ cnt = cnt_file(cntfile) pre = decode(annotation["attrs"]["samples_pre_event"]) post = decode(annotation["attrs"]["samples_post_event"]) cix = [cnt.get_channel_info(c)[0] for c in range(cnt.get_channel_count()) ].index(decode(annotation["attrs"]["channel_of_interest"])[0]) traces = [] for attrs in annotation["traces"]: onset = decode(attrs["event_sample"]) trace = cnt.get_samples(fro=onset - pre, to=onset + post) trace = np.asanyarray(trace)[:, cix] traces.append(trace) return traces
def load_triggers(fname: FileName) -> List[Tuple[str, int]]: c = cnt_file(fname) triggers = [c.get_trigger(i) for i in range(c.get_trigger_count())] events = [] for t in triggers: m = t[0] idx = t[1] events.append((m, idx)) return events
def test_add_trigger(tmp_cnt): fname, rate, channel_count = tmp_cnt c = libeep.cnt_file(fname) assert c.get_trigger_count() == 1 for i in range(c.get_trigger_count()): m, s, *_ = c.get_trigger(i) print(m, s) assert m == "hello" assert s == 5
def test_channels(tmp_cnt): fname, rate, channel_count = tmp_cnt c = libeep.cnt_file(fname) assert c.get_channel_count() == channel_count for i in range(c.get_channel_count()): l, r, u = c.get_channel_info(i) assert l == f"Ch{i+1}" assert r == "None" assert u == "uV"
def load_ephys_file( eeg_fname: FileName, emg_fname: FileName, pre_in_ms: float = 100, post_in_ms: float = 100, select_events: List[str] = ["0001"], select_channel: str = "Ch1", ) -> Dict[str, Any]: """load the electophysiological data for a specific channel for a smartmove file-pair args ---- eeg_fname: FileName the path to the EEG file. The file is expected to have the following format: VvNn_VvNn_YYYY-MM-DD_HH-MM-SS.cnt emg_fname: FileName the path to the EMG file. The file is expected to have the following format: VvNn<qualifier> YYYY-MM-DD_HH-MM-SS.cnt pre_in_ms: float = 100 how much time before the TMS post_in_ms: float = 100 how much time after the TMS select_events: List[str] = ["0001"] which events indicate the occurence of a TMS-pulse select_channel: str = "Ch1" the channel to use. Note that EMG channel labes only offer a selection of :code:`'Ch1', 'Ch2', 'Ch3', 'Ch4', 'Ch5', 'Ch6', 'Ch7', 'Ch8'`, while EEG channels are recording from a standard wavecap and should offer ['Fp1', 'Fpz', 'Fp2', 'F7', 'F3', 'Fz', 'F4', 'F8', 'FC5', 'FC1', 'FC2', 'FC6', 'M1', 'T7', 'C3', 'Cz', 'C4', 'T8', 'M2', 'CP5', 'CP1', 'CP2', 'CP6', 'P7', 'P3', 'Pz', 'P4', 'P8', 'POz', 'O1', 'O2', 'EOG', 'AF7', 'AF3', 'AF4', 'AF8', 'F5', 'F1', 'F2', 'F6', 'FC3', 'FCz', 'FC4', 'C5', 'C1', 'C2', 'C6', 'CP3', 'CP4', 'P5', 'P1', 'P2', 'P6', 'PO5', 'PO3', 'PO4', 'PO6', 'FT7', 'FT8', 'TP7', 'TP8', 'PO7', 'PO8', 'Oz'] all in reference to Cpz. returns ------- Traces: List[TraceData] the TraceData for each event fitting to select_events in the file pre_in_samples:int how many samples before the trigger post_in_samples:int how many samples after the trigger sampling_rate: float the sampling rate of the trace """ if not is_eegfile_valid(eeg_fname): raise ValueError( f"{eeg_fname} has not the correct file signature for a smartmove eeg file" ) assert_equal_recording_day(eeg_fname, emg_fname) filedate = str(parse_recording_date(eeg_fname)) eeg = cnt_file(eeg_fname) eeg_labels = [ eeg.get_channel_info(i)[0] for i in range(eeg.get_channel_count()) ] emg = cnt_file(emg_fname) emg_labels = [ emg.get_channel_info(i)[0] for i in range(emg.get_channel_count()) ] # the triggers are always recorded with EEG, so we need this Fs triggers = load_triggers(eeg_fname) eeg_fs = eeg.get_sample_frequency() if select_channel in eeg_labels: cnt = eeg elif select_channel in emg_labels: cnt = emg else: raise IndexError(f"Selected channel {select_channel} not found") origin = Path(cnt._fname).name subject = Path(eeg_fname).stem.split("_")[0] fs = cnt.get_sample_frequency() pre = int(pre_in_ms * fs / 1000) post = int(post_in_ms * fs / 1000) scale = fs / eeg_fs enames = [] onsets = [] tstamps = [] for event, sample in triggers: if event in select_events: onset = int(sample * scale) onsets.append(onset) tstamps.append(sample / eeg_fs) enames.append(event) print("Selected", len(onsets), "of", len(triggers), "trigger events") time_since_last_pulse = [inf] + [ a - b for a, b in zip(tstamps[1:], tstamps[0:-1]) ] info = { "origin": origin, "event_samples": onsets, "event_times": tstamps, "event_names": enames, "samples_pre_event": pre, "samples_post_event": post, "samplingrate": fs, "subject": subject, "channel_labels": [select_channel], "time_since_last_pulse": time_since_last_pulse, "filedate": filedate, } return info
def prepare_annotations( fname, docname, pre_in_ms=100, post_in_ms=100, select_events=["4"], select_channel="Ch1", mso=100, didt="", ): if not Path(fname).with_suffix(".evt").exists(): raise FileNotFoundError("No matching event file found for", fname) coords, subject = load_documentation_txt(docname) recstart = get_recording_start(fname) cnt = cnt_file(fname) fs = cnt.get_sample_frequency() pre_in_samples = int(pre_in_ms * fs / 1000) post_in_samples = int(post_in_ms * fs / 1000) event_samples = [] event_times = [] event_names = [] stimulation_intensity_mso = [] stimulation_intensity_didt = [] print(f"Reading events constrained to {select_events}") for tix in range(cnt.get_trigger_count()): event_name, event_sample, *suppl = cnt.get_trigger(tix) if event_name in select_events: event_names.append(event_name) event_samples.append(event_sample) event_time = event_sample / fs event_times.append(event_time) stimulation_intensity_mso.append(mso) stimulation_intensity_didt.append(didt) print(f"Assigning coordinates from {docname}") xyz_coords = [] for event_time in event_times: event_datetime = recstart + timedelta(seconds=event_time) xyz = [nan, nan, nan] for idx, (key, _xyz) in enumerate(coords.items()): delta = (key - event_datetime).total_seconds() if delta > 0: xyz = _xyz break xyz_coords.append(xyz) time_since_last_pulse = [ a - b for a, b in zip(event_times, [-inf] + event_times) ] anno = AnnotationFactory(readin="tms", readout="cmep", origin=Path(fname).name) anno.set("filedate", str(recstart)) anno.set("subject", subject) anno.set("samplingrate", fs) anno.set("samples_pre_event", pre_in_samples) anno.set("samples_post_event", post_in_samples) anno.set("channel_of_interest", [select_channel]) anno.set("channel_labels", [select_channel]) # trace fields for idx, t in enumerate(event_samples): tattr = { "id": idx, "event_name": event_names[idx], "event_sample": event_samples[idx], "event_time": event_times[idx], "xyz_coords": xyz_coords[idx], "time_since_last_pulse_in_s": time_since_last_pulse[idx], "stimulation_intensity_mso": stimulation_intensity_mso[idx], "stimulation_intensity_didt": stimulation_intensity_didt[idx], "reject": False, "onset_shift": 0, } anno.append_trace_attr(tattr) return anno.anno
def test_add_samples(tmp_cnt): fname, rate, channel_count = tmp_cnt c = libeep.cnt_file(fname) data = c.get_samples(0, c.get_sample_count()) assert all([x == 0.0 for x in data[0]]) assert all([x == 99.0 for x in data[-1]])
def test_sampling_rate(tmp_cnt): fname, rate, channel_count = tmp_cnt c = libeep.cnt_file(fname) assert c.get_sample_frequency() == rate