def test_montage_file_lookup(subject, localization, montage, rhino_root): finder = PathFinder(subject, localization=localization, montage=montage, rootdir=rhino_root) path = finder.find('pairs') assert path is not None
def save_coords_for_blender(subject_id: str, localization: int, outdir: str, localization_file: Optional[str] = None, rootdir: Optional[str] = "/"): """ Generates a CSV file with metadata about contacts in the subject's montage Parameters ---------- subject_id: str ID of the subject localization: int Localization number of the subject to use outdir: str Directory to save the resulting file localization_file: str, default None Default is to look up the localization file to use, but if specified, uses the passed file location rootdir: str Mount point for RHINO Returns ------- saved_results_path: str """ use_matlab = False if localization_file is None: finder = PathFinder(subject=subject_id, localization=localization, rootdir=rootdir) try: localization_file = finder.find('localization') except FileNotFoundError: use_matlab = True if use_matlab or not os.path.exists(localization_file): # get coordinates from matlab talstruct subject_localization = subject_id if localization != 0: subject_localization = "_".join([subject_id, str(localization)]) final_df = extract_coordinates_from_talstructs(subject_localization, rootdir=rootdir) else: # get coordinates from the new localization.json file final_df = extract_coordinates_from_localization_file(localization_file) # Scale coordinates for col in ['x', 'y', 'z']: final_df[col] = OBJ_SCALE_FACTOR * final_df[col] final_df = add_orientation_contact_for_depth_electrodes(final_df) final_df = final_df[["contact_name", "contact_type", "x", "y", "z", "atlas", "orient_to"]] final_df.to_csv(outdir + "/electrode_coordinates.csv", index=False) return os.path.join(outdir, "electrode_coordinates.csv")
def load(self, **kwargs): """Overrides the generic load method so as to accept keyword arguments to pass along to :meth:`as_timeseries`. """ if "events" in kwargs: events = kwargs["events"] else: if self.session is None: raise ValueError( "A session must be specified to load an entire session of " "EEG data!") finder = PathFinder(subject=self.subject, experiment=self.experiment, session=self.session, rootdir=self.rootdir) events_file = finder.find("task_events") all_events = EventReader.fromfile(events_file, self.subject, self.experiment, self.session) # Select only a single event with a valid eegfile just to get the # filename valid = all_events[(all_events["eegfile"].notnull()) & (all_events["eegfile"].str.len() > 0)] events = pd.DataFrame(valid.iloc[0]).T.reset_index(drop=True) # Set relative start and stop times if necessary. If they were # already specified, these will allow us to subset the session. if "rel_start" not in kwargs: kwargs["rel_start"] = 0 if "rel_stop" not in kwargs: kwargs["rel_stop"] = -1 if not len(events): raise ValueError("No events found! Hint: did filtering events " "result in at least one?") elif len(events["subject"].unique()) > 1: raise ValueError("Loading multiple sessions of EEG data requires " "using events from only a single subject.") if "rel_start" not in kwargs or "rel_stop" not in kwargs: raise exc.IncompatibleParametersError( "rel_start and rel_stop must be given with events") # info = EEGMetaReader.fromfile(path, subject=self.subject) # sample_rate = info["sample_rate"] # dtype = info["data_format"] self.scheme = kwargs.get("scheme", None) events = self._eegfile_absolute(events.copy()) return self.as_timeseries(events, kwargs["rel_start"], kwargs["rel_stop"])
def test_session_params(rhino_root, use_basename): subject = 'TJ012' eeg_basename = 'TJ012_20Apr10_1329' if use_basename: finder = PathFinder(subject=subject, eeg_basename=eeg_basename, rootdir=rhino_root) else: finder = PathFinder(subject=subject, rootdir=rhino_root) path = finder.find('sources') assert (eeg_basename in path) == use_basename
def get_bad_channel_names(subject, montage, just_bad=None, rhino_root='/'): finder = PathFinder(rootdir=rhino_root, subject=subject, montage=montage) fn = finder.find('electrode_categories') with open(fn, 'r') as fh: lines = [mystr.replace('\n', '') for mystr in fh.readlines()] if just_bad is True: bidx = len(lines) try: bidx = [s.lower().replace(':', '').strip() for s in lines].index('bad electrodes') except: try: bidx = [s.lower().replace(':', '').strip() for s in lines].index('broken leads') except: lines = [] lines = lines[bidx:] return lines
def as_timeseries(self, events: pd.DataFrame, rel_start: Union[float, int], rel_stop: Union[float, int]) -> EEGContainer: """Read the timeseries. Parameters ---------- events Events to read EEG data from rel_start Relative start times in ms rel_stop Relative stop times in ms Returns ------- A time series with shape (channels, epochs, time). By default, this returns data as it was physically recorded (e.g., if recorded with a common reference, each channel will be a contact's reading referenced to the common reference, a.k.a. "monopolar channels"). Raises ------ RereferencingNotPossibleError When rereferencing is not possible. """ eegs = [] # sanity check on the offsets if rel_start != 0 and rel_stop != -1 and rel_start > rel_stop: raise ValueError('rel_start must precede rel_stop') for filename in events["eegfile"].unique(): # select subset of events for this basename ev = events[events["eegfile"] == filename] # determine experiment, session, dtype, and sample rate experiment = ev["experiment"].unique()[0] session = ev["session"].unique()[0] basename = os.path.basename(filename) finder = PathFinder(subject=self.subject, experiment=experiment, session=session, eeg_basename=basename, rootdir=self.rootdir) sources = EEGMetaReader.fromfile(finder.find("sources"), subject=self.subject) sample_rate = sources["sample_rate"] dtype = sources["data_format"] is_scalp = dtype in (".bdf", ".raw", ".mff") # Convert events to epochs (onset & offset times) if rel_start == 0 and rel_stop == -1 and len(events) == 1: epochs = [(0, None)] else: epochs = convert.events_to_epochs(ev, rel_start, rel_stop, sample_rate) # Scalp EEG reader requires onsets, rel_start (in sec), and rel_ # stop (in sec) to cut data into epochs if is_scalp: on_off_epochs = epochs # The onset & offset times will still # be passed to the EEGContainer later if rel_start == 0 and rel_stop == -1 and len(events) == 1: epochs = None else: epochs = np.zeros((len(ev), 3), dtype=int) epochs[:, 0] = ev["eegoffset"] epochs = dict(epochs=epochs, tmin=rel_start / 1000., tmax=rel_stop / 1000.) root = get_root_dir(self.rootdir) eeg_filename = os.path.join(root, filename.lstrip("/")) reader_class = self._get_reader_class(filename) reader = reader_class(filename=eeg_filename, dtype=dtype, epochs=epochs, scheme=self.scheme, clean=self.clean) # if scalp EEG, info is an MNE Info object; if iEEG, info is a list # of contacts data, info = reader.read() attrs = {} if is_scalp: # Pass MNE info and events as extra attributes, to be able to # fully reconstruct MNE Raw/Epochs objects attrs["mne_info"] = info channels = info["ch_names"] if epochs is not None: # Crop out any events/epoch times that ran beyond the # bounds of the EEG recording te_pre = info["truncated_events_pre"] \ if info["truncated_events_pre"] > 0 else None te_post = -info["truncated_events_post"] \ if info["truncated_events_post"] > 0 else None on_off_epochs = on_off_epochs[te_pre:te_post] ev = ev[te_pre:te_post] # Pass the onset & offset time epoch list to EEGContainer, NOT # the MNE-formatted epoch list epochs = on_off_epochs elif self.scheme is not None: data, channels = reader.rereference(data, info) else: channels = ["CH{}".format(n + 1) for n in range(data.shape[1])] eegs.append( EEGContainer( data, sample_rate, epochs=epochs, events=ev, channels=channels, tstart=rel_start, attrs=attrs ) ) eegs = EEGContainer.concatenate(eegs) eegs.attrs["rereferencing_possible"] = reader.rereferencing_possible return eegs
def as_timeseries(self, events: pd.DataFrame, rel_start: Union[float, int], rel_stop: Union[float, int]) -> EEGContainer: """Read the timeseries. Parameters ---------- events Events to read EEG data from rel_start Relative start times in ms rel_stop Relative stop times in ms Returns ------- A time series with shape (channels, epochs, time). By default, this returns data as it was physically recorded (e.g., if recorded with a common reference, each channel will be a contact's reading referenced to the common reference, a.k.a. "monopolar channels"). Raises ------ RereferencingNotPossibleError When rereferencing is not possible. """ eegs = [] for filename in events["eegfile"].unique(): # select subset of events for this basename ev = events[events["eegfile"] == filename] # determine experiment, session, dtype, and sample rate experiment = ev["experiment"].unique()[0] session = ev["session"].unique()[0] finder = PathFinder(subject=self.subject, experiment=experiment, session=session, rootdir=self.rootdir) sources = EEGMetaReader.fromfile(finder.find("sources"), subject=self.subject) sample_rate = sources["sample_rate"] dtype = sources["data_format"] # convert events to epochs if rel_stop < 0: # We're trying to load to the end of the session. Only allow # this in cases where we're trying to load a whole session. if len(events) > 1: raise ValueError("rel_stop must not be negative") epochs = [(0, None)] else: epochs = convert.events_to_epochs(ev, rel_start, rel_stop, sample_rate) root = get_root_dir(self.rootdir) eeg_filename = os.path.join(root, filename.lstrip("/")) reader_class = self._get_reader_class(filename) reader = reader_class(filename=eeg_filename, dtype=dtype, epochs=epochs, scheme=self.scheme) data, contacts = reader.read() if self.scheme is not None: data, labels = reader.rereference(data, contacts) channels = labels else: channels = ["CH{}".format(n + 1) for n in range(data.shape[1])] eegs.append( EEGContainer( data, sample_rate, epochs=epochs, events=ev, channels=channels, tstart=rel_start, )) eegs = EEGContainer.concatenate(eegs) eegs.attrs["rereferencing_possible"] = reader.rereferencing_possible return eegs