From 6d802e932afb15f5b001bc7002fb6310b17020a7 Mon Sep 17 00:00:00 2001 From: withmywoessner Date: Sat, 27 Jan 2024 21:39:12 -0600 Subject: [PATCH 1/9] Create functions to read Neuroscan formats --- mne/io/cnt/__init__.py | 2 +- mne/io/cnt/_utils.py | 19 +- mne/io/cnt/cnt.py | 385 ++++++++++++++++++++++++++++++++++++++++- 3 files changed, 400 insertions(+), 6 deletions(-) diff --git a/mne/io/cnt/__init__.py b/mne/io/cnt/__init__.py index d85b6bce1db..7ac2b38e1b0 100644 --- a/mne/io/cnt/__init__.py +++ b/mne/io/cnt/__init__.py @@ -2,4 +2,4 @@ # Copyright the MNE-Python contributors. """CNT data reader.""" -from .cnt import read_raw_cnt +from .cnt import read_raw_cnt, read_epochs_cnt, read_evoked_cnt diff --git a/mne/io/cnt/_utils.py b/mne/io/cnt/_utils.py index 86842ad60c6..3601803f6dc 100644 --- a/mne/io/cnt/_utils.py +++ b/mne/io/cnt/_utils.py @@ -73,6 +73,20 @@ def _read_teeg(f, teeg_offset): ) +EpochHeader = namedtuple( + "EpochHeader", + ( + "Accept Ttype Correct Rt Response Reserved" + ), +) +# char Accept; /* accept byte */ +# short Ttype; /* trial type */ +# short Correct; /* accuracy */ +# float Rt; /* reaction time */ +# short Response; /* response type */ +# short Reserved; /* not used */ + + def _get_event_parser(event_type): if event_type == 1: event_maker = CNTEventType1 @@ -83,8 +97,11 @@ def _get_event_parser(event_type): elif event_type == 3: event_maker = CNTEventType3 struct_pattern = " "RawCNT": + r"""Reader function for Neuroscan ``.eeg`` epochs files. + + Parameters + ---------- + input_fname : path-like + Path to the Neuroscan ``.eeg`` file. + events : ndarray, shape (n_events, 3) | None + Path to events file. If array, it is the events typically returned + by the read_events function. If some events don't match the events + of interest as specified by event_id, they will be marked as 'IGNORED' + in the drop log. If None, it is constructed from the Neuroscan (.eeg) + file with each unique event encoded with a different integer. + event_id : int | list of int | dict | None + The id of the event to consider. If dict, the keys can later be used + to access associated events. + Example:: + + {"auditory":1, "visual":3} + + If int, a dict will be created with + the id as string. If a list, all events with the IDs specified + in the list are used. If None, the event_id is constructed from the + Neuroscan (.eeg) file with each descriptions copied from ``eventtype``. + eog : list | tuple | 'auto' + Names or indices of channels that should be designated EOG channels. + If 'auto', the channel names containing ``EOG`` or ``EYE`` are used. + Defaults to empty tuple. + %(uint16_codec)s + %(montage_units)s + %(verbose)s + + Returns + ------- + epochs : instance of Epochs + The epochs. + + See Also + -------- + mne.Epochs : Documentation of attributes and methods. + + + """ + + epochs = EpochsCNT( + input_fname=input_fname, + events=events, + event_id=event_id, + eog=eog, + misc=misc, + ecg=ecg, + emg=emg, + data_format=data_format, + date_format=date_format, + header=header, + verbose=verbose, + ) + return epochs + + +def read_evoked_cnt(fname, info, comment=None): + """Load evoked data from a Neuroscan .avg timelocked structure. + + This function expects to find timelocked data in the structure data_name is + pointing at. + + .. warning:: FieldTrip does not normally store the original information + concerning channel location, orientation, type etc. It is + therefore **highly recommended** to provide the info field. + This can be obtained by reading the original raw data file + with MNE functions (without preload). The returned object + contains the necessary info field. + + Parameters + ---------- + fname : path-like + Path and filename of the ``.mat`` file containing the data. + info : dict or None + The info dict of the raw data file corresponding to the data to import. + If this is set to None, information is extracted from the + Neuroscan structure. + comment : str + Comment on dataset. Can be the condition. + + Returns + ------- + evoked : instance of EvokedArray + An EvokedArray containing the loaded data. + """ + + info, cnt_info = _get_cnt_info(input_fname, eog, ecg, emg, misc, data_format, date_format, header, mode = 'evoked') + + input_fname = cnt_info["input_fname"] + + # number of points + n_pnts = cnt_info['n_pnts'] + n_channels = cnt_info["orig_nchan"] + cals = cnt_info["cals_avg"] + accepted_epochs = int(cnt_info['accepted_epochs']) + + data = np.empty((n_channels, n_pnts), dtype=float) + UNUSED_HEAD_SIZE = 5 + DATA_POINT_SIZE = 4 + + with open(input_fname, 'rb') as f: + # Ensure the file pointer is at the beginning of the EEG data + data_start = 900 + n_channels * 75 + data_end = data_start + (n_channels * (5 + n_pnts * DATA_POINT_SIZE)) + data_step = 5 + n_pnts * DATA_POINT_SIZE + + for chan, i in enumerate(range(data_start, data_end, data_step)): + f.seek(i) + data_points = np.fromfile(f, dtype='>f', count=n_pnts, offset=UNUSED_HEAD_SIZE) + # Scale the data to physical units in Volts + data[chan] = data_points * cals[chan] / accepted_epochs * 1e-6 + + evoked = EvokedArray(data, info, comment=comment) + return evoked + + +def _get_cnt_info(input_fname, eog, ecg, emg, misc, data_format, date_format, header, mode = 'raw'): """Read the cnt header.""" data_offset = 900 # Size of the 'SETUP' header. cnt_info = dict() @@ -294,11 +438,22 @@ def _get_cnt_info(input_fname, eog, ecg, emg, misc, data_format, date_format, he session_date = "%s %s" % (read_str(fid, 10), read_str(fid, 12)) meas_date = _session_date_2_meas_date(session_date, date_format) + if mode == 'epoch': + fid.seek(362) + cnt_info['n_epochs'] = np.fromfile(fid, dtype="= 0] @@ -347,8 +502,10 @@ def _get_cnt_info(input_fname, eog, ecg, emg, misc, data_format, date_format, he cnt_info["channel_offset"] //= n_bytes else: cnt_info["channel_offset"] = 1 - - ch_names, cals, baselines, chs, pos = (list(), list(), list(), list(), list()) + if mode == 'evoked': + ch_names, cals, baselines, chs, pos, cals_avg = (list(), list(), list(), list(), list(), list()) + else: + ch_names, cals, baselines, chs, pos = (list(), list(), list(), list(), list()) bads = list() _validate_type(header, str, "header") @@ -379,6 +536,8 @@ def _get_cnt_info(input_fname, eog, ecg, emg, misc, data_format, date_format, he sensitivity = np.fromfile(fid, dtype="f4", count=1).item() fid.seek(data_offset + 75 * ch_idx + 71) cal = np.fromfile(fid, dtype="f4", count=1).item() + if mode == 'evoked': + cals_avg.append(cal) cals.append(cal * sensitivity * 1e-6 / 204.8) info = _empty_info(sfreq) @@ -394,6 +553,8 @@ def _get_cnt_info(input_fname, eog, ecg, emg, misc, data_format, date_format, he "last_name": last_name, } + if mode == 'evoked': + cnt_info['cals_avg'] = cals_avg if eog == "auto": eog = _find_channels(ch_names, "EOG") if ecg == "auto": @@ -599,3 +760,219 @@ def _read_segment_file(self, data, idx, fi, start, stop, cals, mult): one[idx] -= baselines[idx][:, None] _mult_cal_one(data[:, sample_start:sample_stop], one, idx, cals, mult) + + +class EpochsCNT(BaseEpochs): + r"""Epochs from EEGLAB .set file. + + Parameters + ---------- + input_fname : path-like + Path to the ``.eeg`` epochs file. + events : path-like | array, shape (n_events, 3) | None + Path to events file. If array, it is the events typically returned + by the read_events function. If some events don't match the events + of interest as specified by event_id, they will be marked as 'IGNORED' + in the drop log. If None, it is constructed from the EEGLAB (.set) file + with each unique event encoded with a different integer. + event_id : int | list of int | dict | None + The id of the event to consider. If dict, + the keys can later be used to access associated events. Example: + dict(auditory=1, visual=3). If int, a dict will be created with + the id as string. If a list, all events with the IDs specified + in the list are used. If None, the event_id is constructed from the + EEGLAB (.set) file with each descriptions copied from ``eventtype``. + tmin : float + Start time before event. + baseline : None or tuple of length 2 (default (None, 0)) + The time interval to apply baseline correction. + If None do not apply it. If baseline is (a, b) + the interval is between "a (s)" and "b (s)". + If a is None the beginning of the data is used + and if b is None then b is set to the end of the interval. + If baseline is equal to (None, None) all the time + interval is used. + The baseline (a, b) includes both endpoints, i.e. all + timepoints t such that a <= t <= b. + reject : dict | None + Rejection parameters based on peak-to-peak amplitude. + Valid keys are 'grad' | 'mag' | 'eeg' | 'eog' | 'ecg'. + If reject is None then no rejection is done. Example:: + + reject = dict(grad=4000e-13, # T / m (gradiometers) + mag=4e-12, # T (magnetometers) + eeg=40e-6, # V (EEG channels) + eog=250e-6 # V (EOG channels) + ) + flat : dict | None + Rejection parameters based on flatness of signal. + Valid keys are 'grad' | 'mag' | 'eeg' | 'eog' | 'ecg', and values + are floats that set the minimum acceptable peak-to-peak amplitude. + If flat is None then no rejection is done. + reject_tmin : scalar | None + Start of the time window used to reject epochs (with the default None, + the window will start with tmin). + reject_tmax : scalar | None + End of the time window used to reject epochs (with the default None, + the window will end with tmax). + eog : list | tuple | 'auto' + Names or indices of channels that should be designated EOG channels. + If 'auto', the channel names containing ``EOG`` or ``EYE`` are used. + Defaults to empty tuple. + %(uint16_codec)s + %(montage_units)s + %(verbose)s + + See Also + -------- + mne.Epochs : Documentation of attributes and methods. + + Notes + ----- + .. versionadded:: 0.11.0 + """ + + @verbose + def __init__( + self, + input_fname, + events=None, + event_id=None, + tmin=0, + baseline=None, + reject=None, + flat=None, + reject_tmin=None, + reject_tmax=None, + eog=(), + misc=(), + ecg=(), + emg=(), + data_format="auto", + date_format="mm/dd/yy", + uint16_codec=None, + montage_units="auto", + *, + header="auto", + verbose=None, + ): + if isinstance(events, (str, PathLike, Path)): + events = read_events(events) + _check_option("date_format", date_format, ["mm/dd/yy", "dd/mm/yy"]) + if date_format == "dd/mm/yy": + _date_format = "%d/%m/%y %H:%M:%S" + else: + _date_format = "%m/%d/%y %H:%M:%S" + + input_fname = str( + _check_fname(fname=input_fname, must_exist=True, overwrite="read") + ) + logger.info("Extracting .eeg Parameters from %s..." % input_fname) + self.info, cnt_info = _get_cnt_info( + input_fname, eog, ecg, emg, misc, + data_format, _date_format, header, mode='epoch' + ) + + cnt_info.update(input_fname=input_fname) + self._raw_extras = [cnt_info] + self._filenames = [] + epoch_headers, data = self._read_cnt_epochs_data() + print(epoch_headers) + events = [event[1] for event in epoch_headers] + print(events) + if event_id is None: # convert to int to make typing-checks happy + event_id = {str(e): int(e) for e in np.unique(events)} + print(event_id) + if not ( + (events is None and event_id is None) + or (events is not None and event_id is not None) + ): + raise ValueError( + "Both `events` and `event_id` must be " + "None or not None") + + + # if eeg.trials <= 1: + # raise ValueError( + # "The file does not seem to contain epochs " + # "(trials less than 2). " + # "You should try using read_raw_cnt function." + # ) + + assert data.shape == ( + self._raw_extras[0]["n_epochs"], + self.info["nchan"], + self._raw_extras[0]["n_pnts"], + ) + tmax = ((data.shape[2] - 1) / self.info["sfreq"]) + tmin + + super().__init__( + self.info, + data, + events, + event_id, + tmin, + tmax, + baseline, + reject=reject, + flat=flat, + reject_tmin=reject_tmin, + reject_tmax=reject_tmax, + filename=input_fname, + verbose=verbose, + ) + + # data are preloaded but _bad_dropped is not set so we do it here: + self._bad_dropped = True + + _set_dig_montage_in_init(self, eeg_montage) + + logger.info("Ready.") + + def _read_cnt_epochs_data(self): + + """Read epochs data from .eeg file.""" + + cnt_info = self._raw_extras[0] + info = self.info + + input_fname = cnt_info["input_fname"] + + n_epochs = cnt_info['n_epochs'] + # number of points per epoch + n_pnts = cnt_info['n_pnts'] + n_channels = cnt_info["orig_nchan"] + cals = [d['cal'] for d in info["chs"]] + + data = np.empty((n_epochs, n_channels, n_pnts), dtype=float) + epoch_headers = [] + SWEEP_HEAD_SIZE = 13 + DATA_POINT_SIZE = 4 + + with open(input_fname, 'rb') as f: + # Ensure the file pointer is at the beginning of the EEG data + data_start = 900 + n_channels * 75 + data_end = data_start + n_epochs * (SWEEP_HEAD_SIZE + n_pnts * n_channels * DATA_POINT_SIZE) + data_step = SWEEP_HEAD_SIZE + n_pnts * n_channels * DATA_POINT_SIZE + + for epoch, i in enumerate(range(data_start, data_end, data_step)): + epoch_header = np.fromfile( + f, dtype=np.dtype(' Date: Sun, 28 Jan 2024 03:42:52 +0000 Subject: [PATCH 2/9] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- mne/io/cnt/_utils.py | 6 +- mne/io/cnt/cnt.py | 135 ++++++++++++++++++++++++++----------------- 2 files changed, 85 insertions(+), 56 deletions(-) diff --git a/mne/io/cnt/_utils.py b/mne/io/cnt/_utils.py index 3601803f6dc..30135c6e09c 100644 --- a/mne/io/cnt/_utils.py +++ b/mne/io/cnt/_utils.py @@ -75,9 +75,7 @@ def _read_teeg(f, teeg_offset): EpochHeader = namedtuple( "EpochHeader", - ( - "Accept Ttype Correct Rt Response Reserved" - ), + ("Accept Ttype Correct Rt Response Reserved"), ) # char Accept; /* accept byte */ # short Ttype; /* trial type */ @@ -97,7 +95,7 @@ def _get_event_parser(event_type): elif event_type == 3: event_maker = CNTEventType3 struct_pattern = "= 0] @@ -502,10 +514,23 @@ def _get_cnt_info(input_fname, eog, ecg, emg, misc, data_format, date_format, he cnt_info["channel_offset"] //= n_bytes else: cnt_info["channel_offset"] = 1 - if mode == 'evoked': - ch_names, cals, baselines, chs, pos, cals_avg = (list(), list(), list(), list(), list(), list()) + if mode == "evoked": + ch_names, cals, baselines, chs, pos, cals_avg = ( + list(), + list(), + list(), + list(), + list(), + list(), + ) else: - ch_names, cals, baselines, chs, pos = (list(), list(), list(), list(), list()) + ch_names, cals, baselines, chs, pos = ( + list(), + list(), + list(), + list(), + list(), + ) bads = list() _validate_type(header, str, "header") @@ -536,7 +561,7 @@ def _get_cnt_info(input_fname, eog, ecg, emg, misc, data_format, date_format, he sensitivity = np.fromfile(fid, dtype="f4", count=1).item() fid.seek(data_offset + 75 * ch_idx + 71) cal = np.fromfile(fid, dtype="f4", count=1).item() - if mode == 'evoked': + if mode == "evoked": cals_avg.append(cal) cals.append(cal * sensitivity * 1e-6 / 204.8) @@ -553,8 +578,8 @@ def _get_cnt_info(input_fname, eog, ecg, emg, misc, data_format, date_format, he "last_name": last_name, } - if mode == 'evoked': - cnt_info['cals_avg'] = cals_avg + if mode == "evoked": + cnt_info["cals_avg"] = cals_avg if eog == "auto": eog = _find_channels(ch_names, "EOG") if ecg == "auto": @@ -869,8 +894,15 @@ def __init__( ) logger.info("Extracting .eeg Parameters from %s..." % input_fname) self.info, cnt_info = _get_cnt_info( - input_fname, eog, ecg, emg, misc, - data_format, _date_format, header, mode='epoch' + input_fname, + eog, + ecg, + emg, + misc, + data_format, + _date_format, + header, + mode="epoch", ) cnt_info.update(input_fname=input_fname) @@ -887,10 +919,7 @@ def __init__( (events is None and event_id is None) or (events is not None and event_id is not None) ): - raise ValueError( - "Both `events` and `event_id` must be " - "None or not None") - + raise ValueError("Both `events` and `event_id` must be " "None or not None") # if eeg.trials <= 1: # raise ValueError( @@ -930,49 +959,51 @@ def __init__( logger.info("Ready.") def _read_cnt_epochs_data(self): - """Read epochs data from .eeg file.""" - cnt_info = self._raw_extras[0] info = self.info input_fname = cnt_info["input_fname"] - n_epochs = cnt_info['n_epochs'] + n_epochs = cnt_info["n_epochs"] # number of points per epoch - n_pnts = cnt_info['n_pnts'] + n_pnts = cnt_info["n_pnts"] n_channels = cnt_info["orig_nchan"] - cals = [d['cal'] for d in info["chs"]] + cals = [d["cal"] for d in info["chs"]] data = np.empty((n_epochs, n_channels, n_pnts), dtype=float) epoch_headers = [] SWEEP_HEAD_SIZE = 13 DATA_POINT_SIZE = 4 - with open(input_fname, 'rb') as f: + with open(input_fname, "rb") as f: # Ensure the file pointer is at the beginning of the EEG data data_start = 900 + n_channels * 75 - data_end = data_start + n_epochs * (SWEEP_HEAD_SIZE + n_pnts * n_channels * DATA_POINT_SIZE) + data_end = data_start + n_epochs * ( + SWEEP_HEAD_SIZE + n_pnts * n_channels * DATA_POINT_SIZE + ) data_step = SWEEP_HEAD_SIZE + n_pnts * n_channels * DATA_POINT_SIZE for epoch, i in enumerate(range(data_start, data_end, data_step)): epoch_header = np.fromfile( - f, dtype=np.dtype(' Date: Sun, 28 Jan 2024 03:18:11 -0600 Subject: [PATCH 3/9] Update cnt.py --- mne/io/cnt/cnt.py | 65 ++++++++++++++++++++++++++++------------------- 1 file changed, 39 insertions(+), 26 deletions(-) diff --git a/mne/io/cnt/cnt.py b/mne/io/cnt/cnt.py index 75ed93e26b7..7351429ba41 100644 --- a/mne/io/cnt/cnt.py +++ b/mne/io/cnt/cnt.py @@ -453,7 +453,7 @@ def _get_cnt_info(input_fname, eog, ecg, emg, misc, data_format, date_format, he sfreq = np.fromfile(fid, dtype="= 0] @@ -534,6 +534,7 @@ def _get_cnt_info(input_fname, eog, ecg, emg, misc, data_format, date_format, he baselines.append(np.fromfile(fid, dtype="i2", count=1).item()) fid.seek(data_offset + 75 * ch_idx + 59) sensitivity = np.fromfile(fid, dtype="f4", count=1).item() + print(f'sensitivity: {sensitivity}') fid.seek(data_offset + 75 * ch_idx + 71) cal = np.fromfile(fid, dtype="f4", count=1).item() if mode == 'evoked': @@ -856,8 +857,7 @@ def __init__( header="auto", verbose=None, ): - if isinstance(events, (str, PathLike, Path)): - events = read_events(events) + _check_option("date_format", date_format, ["mm/dd/yy", "dd/mm/yy"]) if date_format == "dd/mm/yy": _date_format = "%d/%m/%y %H:%M:%S" @@ -878,10 +878,10 @@ def __init__( self._filenames = [] epoch_headers, data = self._read_cnt_epochs_data() print(epoch_headers) - events = [event[1] for event in epoch_headers] + events = self._epoch_event_parser(epoch_headers, data) print(events) if event_id is None: # convert to int to make typing-checks happy - event_id = {str(e): int(e) for e in np.unique(events)} + event_id = {str(e): int(e) for e in np.unique(events.T[2])} print(event_id) if not ( (events is None and event_id is None) @@ -890,14 +890,6 @@ def __init__( raise ValueError( "Both `events` and `event_id` must be " "None or not None") - - - # if eeg.trials <= 1: - # raise ValueError( - # "The file does not seem to contain epochs " - # "(trials less than 2). " - # "You should try using read_raw_cnt function." - # ) assert data.shape == ( self._raw_extras[0]["n_epochs"], @@ -925,8 +917,6 @@ def __init__( # data are preloaded but _bad_dropped is not set so we do it here: self._bad_dropped = True - _set_dig_montage_in_init(self, eeg_montage) - logger.info("Ready.") def _read_cnt_epochs_data(self): @@ -951,28 +941,51 @@ def _read_cnt_epochs_data(self): with open(input_fname, 'rb') as f: # Ensure the file pointer is at the beginning of the EEG data + data_start = 900 + n_channels * 75 data_end = data_start + n_epochs * (SWEEP_HEAD_SIZE + n_pnts * n_channels * DATA_POINT_SIZE) data_step = SWEEP_HEAD_SIZE + n_pnts * n_channels * DATA_POINT_SIZE for epoch, i in enumerate(range(data_start, data_end, data_step)): + # Epoch Header: + # char Accept; /* accept byte */ + # short Ttype; /* trial type */ + # short Correct; /* accuracy */ + # float Rt; /* reaction time */ + # short Response; /* response type */ + # short Reserved; /* not used */ + f.seek(i) epoch_header = np.fromfile( f, dtype=np.dtype(' Date: Sun, 28 Jan 2024 09:22:04 +0000 Subject: [PATCH 4/9] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- mne/io/cnt/cnt.py | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/mne/io/cnt/cnt.py b/mne/io/cnt/cnt.py index 3679c678342..36e9bd024ed 100644 --- a/mne/io/cnt/cnt.py +++ b/mne/io/cnt/cnt.py @@ -465,7 +465,7 @@ def _get_cnt_info( sfreq = np.fromfile(fid, dtype="= 0] @@ -559,7 +559,7 @@ def _get_cnt_info( baselines.append(np.fromfile(fid, dtype="i2", count=1).item()) fid.seek(data_offset + 75 * ch_idx + 59) sensitivity = np.fromfile(fid, dtype="f4", count=1).item() - print(f'sensitivity: {sensitivity}') + print(f"sensitivity: {sensitivity}") fid.seek(data_offset + 75 * ch_idx + 71) cal = np.fromfile(fid, dtype="f4", count=1).item() if mode == "evoked": @@ -882,7 +882,6 @@ def __init__( header="auto", verbose=None, ): - _check_option("date_format", date_format, ["mm/dd/yy", "dd/mm/yy"]) if date_format == "dd/mm/yy": _date_format = "%d/%m/%y %H:%M:%S" @@ -919,9 +918,7 @@ def __init__( (events is None and event_id is None) or (events is not None and event_id is not None) ): - raise ValueError( - "Both `events` and `event_id` must be " - "None or not None") + raise ValueError("Both `events` and `event_id` must be " "None or not None") assert data.shape == ( self._raw_extras[0]["n_epochs"], @@ -988,7 +985,8 @@ def _read_cnt_epochs_data(self): # short Reserved; /* not used */ f.seek(i) epoch_header = np.fromfile( - f, dtype=np.dtype(' Date: Sun, 28 Jan 2024 03:43:10 -0600 Subject: [PATCH 5/9] Transpose data --- mne/io/cnt/cnt.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/mne/io/cnt/cnt.py b/mne/io/cnt/cnt.py index 3679c678342..c4d48740144 100644 --- a/mne/io/cnt/cnt.py +++ b/mne/io/cnt/cnt.py @@ -1001,7 +1001,8 @@ def _read_cnt_epochs_data(self): count=n_pnts * n_channels, ) print(data_points) - data_points = data_points.reshape((n_channels, n_pnts), order='C') + data_points = data_points.reshape( + (n_pnts, n_channels), order='C').T # Convert the data points to physical units in Volts data[epoch, :, :] = data_points * cals[0] print(epoch_headers) From c9fe1fba4f605925d9af371443eca4fe3be12c7e Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Sun, 28 Jan 2024 09:44:48 +0000 Subject: [PATCH 6/9] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- mne/io/cnt/cnt.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/mne/io/cnt/cnt.py b/mne/io/cnt/cnt.py index 6ad09b267fd..0fffcf56524 100644 --- a/mne/io/cnt/cnt.py +++ b/mne/io/cnt/cnt.py @@ -1000,8 +1000,7 @@ def _read_cnt_epochs_data(self): count=n_pnts * n_channels, ) print(data_points) - data_points = data_points.reshape( - (n_pnts, n_channels), order='C').T + data_points = data_points.reshape((n_pnts, n_channels), order="C").T # Convert the data points to physical units in Volts data[epoch, :, :] = data_points * cals[0] print(epoch_headers) From a1f15f9ee139efafb78a94b455617b2295738d25 Mon Sep 17 00:00:00 2001 From: withmywoessner Date: Sun, 28 Jan 2024 14:37:48 -0600 Subject: [PATCH 7/9] Delete print statemenst and update documentation --- mne/io/cnt/cnt.py | 91 ++++++++++++++--------------------------------- mne/utils/docs.py | 33 +++++++++++++++++ 2 files changed, 60 insertions(+), 64 deletions(-) diff --git a/mne/io/cnt/cnt.py b/mne/io/cnt/cnt.py index 6ad09b267fd..a05c9cd5065 100644 --- a/mne/io/cnt/cnt.py +++ b/mne/io/cnt/cnt.py @@ -559,7 +559,6 @@ def _get_cnt_info( baselines.append(np.fromfile(fid, dtype="i2", count=1).item()) fid.seek(data_offset + 75 * ch_idx + 59) sensitivity = np.fromfile(fid, dtype="f4", count=1).item() - print(f"sensitivity: {sensitivity}") fid.seek(data_offset + 75 * ch_idx + 71) cal = np.fromfile(fid, dtype="f4", count=1).item() if mode == "evoked": @@ -659,11 +658,7 @@ class RawCNT(BaseRaw): Defaults to ``'auto'``. date_format : ``'mm/dd/yy'`` | ``'dd/mm/yy'`` Format of date in the header. Defaults to ``'mm/dd/yy'``. - header : ``'auto'`` | ``'new'`` | ``'old'`` - Defines the header format. Used to describe how bad channels - are formatted. If auto, reads using old and new header and - if either contain a bad channel make channel bad. - Defaults to ``'auto'``. + %(cnt_header)s %(preload)s stim_channel : bool | None Add a stim channel from the events. Defaults to None to trigger a @@ -789,64 +784,41 @@ def _read_segment_file(self, data, idx, fi, start, stop, cals, mult): class EpochsCNT(BaseEpochs): - r"""Epochs from EEGLAB .set file. + """Epochs from EEGLAB .set file. Parameters ---------- input_fname : path-like Path to the ``.eeg`` epochs file. - events : path-like | array, shape (n_events, 3) | None - Path to events file. If array, it is the events typically returned - by the read_events function. If some events don't match the events - of interest as specified by event_id, they will be marked as 'IGNORED' - in the drop log. If None, it is constructed from the EEGLAB (.set) file - with each unique event encoded with a different integer. - event_id : int | list of int | dict | None - The id of the event to consider. If dict, - the keys can later be used to access associated events. Example: - dict(auditory=1, visual=3). If int, a dict will be created with - the id as string. If a list, all events with the IDs specified - in the list are used. If None, the event_id is constructed from the - EEGLAB (.set) file with each descriptions copied from ``eventtype``. - tmin : float - Start time before event. - baseline : None or tuple of length 2 (default (None, 0)) - The time interval to apply baseline correction. - If None do not apply it. If baseline is (a, b) - the interval is between "a (s)" and "b (s)". - If a is None the beginning of the data is used - and if b is None then b is set to the end of the interval. - If baseline is equal to (None, None) all the time - interval is used. - The baseline (a, b) includes both endpoints, i.e. all - timepoints t such that a <= t <= b. - reject : dict | None - Rejection parameters based on peak-to-peak amplitude. - Valid keys are 'grad' | 'mag' | 'eeg' | 'eog' | 'ecg'. - If reject is None then no rejection is done. Example:: - - reject = dict(grad=4000e-13, # T / m (gradiometers) - mag=4e-12, # T (magnetometers) - eeg=40e-6, # V (EEG channels) - eog=250e-6 # V (EOG channels) - ) - flat : dict | None - Rejection parameters based on flatness of signal. - Valid keys are 'grad' | 'mag' | 'eeg' | 'eog' | 'ecg', and values - are floats that set the minimum acceptable peak-to-peak amplitude. - If flat is None then no rejection is done. - reject_tmin : scalar | None - Start of the time window used to reject epochs (with the default None, - the window will start with tmin). - reject_tmax : scalar | None - End of the time window used to reject epochs (with the default None, - the window will end with tmax). + %(cnt_events)s + %(cnt_event_id)s + %(tmin_epochs)s + %(baseline_epochs)s + %(reject_epochs)s + %(flat)s + %(epochs_reject_tmin_tmax)s eog : list | tuple | 'auto' Names or indices of channels that should be designated EOG channels. If 'auto', the channel names containing ``EOG`` or ``EYE`` are used. Defaults to empty tuple. - %(uint16_codec)s - %(montage_units)s + misc : list | tuple + Names or indices of channels that should be designated MISC channels. + Defaults to empty tuple. + ecg : list | tuple | 'auto' + Names or indices of channels that should be designated ECG channels. + If 'auto', the channel names containing ``ECG`` are used. + Defaults to empty tuple. + emg : list | tuple + Names or indices of channels that should be designated EMG channels. + If 'auto', the channel names containing ``EMG`` are used. + Defaults to empty tuple. + data_format : ``'auto'`` | ``'int16'`` | ``'int32'`` + Defines the data format the data is read in. If ``'auto'``, it is + determined from the file header using ``numsamples`` field. + Defaults to ``'auto'``. + date_format : ``'mm/dd/yy'`` | ``'dd/mm/yy'`` + Format of date in the header. Defaults to ``'mm/dd/yy'``. + %(cnt_header)s %(verbose)s See Also @@ -876,8 +848,6 @@ def __init__( emg=(), data_format="auto", date_format="mm/dd/yy", - uint16_codec=None, - montage_units="auto", *, header="auto", verbose=None, @@ -908,12 +878,9 @@ def __init__( self._raw_extras = [cnt_info] self._filenames = [] epoch_headers, data = self._read_cnt_epochs_data() - print(epoch_headers) events = self._epoch_event_parser(epoch_headers, data) - print(events) if event_id is None: # convert to int to make typing-checks happy event_id = {str(e): int(e) for e in np.unique(events.T[2])} - print(event_id) if not ( (events is None and event_id is None) or (events is not None and event_id is not None) @@ -989,7 +956,6 @@ def _read_cnt_epochs_data(self): dtype=np.dtype(" Date: Sun, 28 Jan 2024 20:43:28 +0000 Subject: [PATCH 8/9] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- mne/io/cnt/cnt.py | 3 +-- mne/utils/docs.py | 3 --- 2 files changed, 1 insertion(+), 5 deletions(-) diff --git a/mne/io/cnt/cnt.py b/mne/io/cnt/cnt.py index a05c9cd5065..03d5f2a8030 100644 --- a/mne/io/cnt/cnt.py +++ b/mne/io/cnt/cnt.py @@ -965,8 +965,7 @@ def _read_cnt_epochs_data(self): dtype=np.dtype(" Date: Tue, 6 Feb 2024 16:10:48 -0600 Subject: [PATCH 9/9] update read_evoked_cnt --- mne/io/cnt/cnt.py | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/mne/io/cnt/cnt.py b/mne/io/cnt/cnt.py index 03d5f2a8030..47044d985f0 100644 --- a/mne/io/cnt/cnt.py +++ b/mne/io/cnt/cnt.py @@ -285,7 +285,7 @@ def read_epochs_cnt( header="auto", verbose=None, ) -> "RawCNT": - r"""Reader function for Neuroscan ``.eeg`` epochs files. + """Reader function for Neuroscan ``.eeg`` epochs files. Parameters ---------- @@ -343,7 +343,14 @@ def read_epochs_cnt( return epochs -def read_evoked_cnt(fname, info, comment=None): +def read_evoked_cnt( + input_fname, + data_format, + date_format, + header, + info=None, + comment=None, +): """Load evoked data from a Neuroscan .avg timelocked structure. This function expects to find timelocked data in the structure data_name is @@ -372,6 +379,7 @@ def read_evoked_cnt(fname, info, comment=None): evoked : instance of EvokedArray An EvokedArray containing the loaded data. """ + eog, ecg, emg, misc = (), (), (), () info, cnt_info = _get_cnt_info( input_fname, eog, @@ -926,6 +934,7 @@ def _read_cnt_epochs_data(self): # number of points per epoch n_pnts = cnt_info["n_pnts"] n_channels = cnt_info["orig_nchan"] + # calibration factors cals = [d["cal"] for d in info["chs"]] data = np.empty((n_epochs, n_channels, n_pnts), dtype=float) @@ -976,6 +985,7 @@ def _epoch_event_parser(self, epoch_headers, data): cnt_info = self._raw_extras[0] sfreq = self.info["sfreq"] tmin = cnt_info["tmin"] + tmax = cnt_info["tmax"] tmax = ((data.shape[2] - 1) / self.info["sfreq"]) + tmin event_time = np.arange(0, len(epoch_headers)) * np.ceil( (tmax - tmin) * sfreq