示例#1
0
    def load_session(self, session_id, kind="eeg", extension="edf"):
        """
        Load in the all runs for the session.

        Parameters
        ----------
        session_id :
        kind :
        extension :

        Returns
        -------
        list[mne.io.BaseRaw]
            A list of objects with raw EEG data for a single session

        """
        # query for the run ids in the session based on factors
        runs_in_session = self.query.get(
            target="run",
            subject=self.subject_id,
            session=self.session_id,
            return_type="id",
            datatype=kind,
            extension=extension,
        )

        # load in all the mne_raw datasets
        rawlist = []
        for run_id in runs_in_session:
            loader = BidsLoader(bids_root=self.bids_root, bids_basename=None)
            rawlist.append(loader.load_dataset())
        return rawlist
示例#2
0
    def _loader(self, runid):
        """
        Create a BidsLoader for a specific runid.

        Parameters
        ----------
        runid : str
            Identifier for the desired recording

        """
        # instantiate a loader/writer
        self.loader = BidsLoader(bids_root=self.bids_root, bids_basename=None)
示例#3
0
    def test_baseio(self):
        test_subjectid = "0001"
        session_id = "seizure"
        kind = "eeg"
        run_id = "01"
        task = "monitor"
        bids_basename = make_bids_basename(
            subject=test_subjectid,
            session=session_id,
            acquisition=kind,
            run=run_id,
            task=task,
            # suffix=kind + ".fif",
        )
        ext = "fif"
        bids_root = os.path.join(os.getcwd(), "./data/bids_layout/")

        # instantiate a loader/writer
        loader = BidsLoader(
            bids_root=bids_root,
            bids_basename=bids_basename,
            kind=kind,
            datatype=ext,
        )
        participant_dict = loader.load_participants_json()
        participant_df = loader.load_participants_tsv()
        scans_df = loader.load_scans_tsv()
        sidecar = loader.load_sidecar_json()
        chans_df = loader.load_channels_tsv()

        # check basic funcs
        print(loader.chanstsv_fpath)
        print(loader.datafile_fpath)
        print(loader.eventstsv_fpath)
        print(loader.rel_chanstsv_fpath)
        print(loader.rel_datafile_fpath)
        print(loader.rel_eventstsv_fpath)
        print(loader.rel_participantsjson_fpath)
        print(loader.rel_participantstsv_fpath)
        print(loader.rel_scanstsv_fpath)
        print(loader.rel_sidecarjson_fpath)

        with tempfile.TemporaryDirectory() as bids_root:
            writer = BidsWriter(
                bids_root=bids_root,
                bids_basename=bids_basename,
                kind=kind,
                datatype=ext,
            )
            with pytest.raises(Exception):
                writer.write_channels_tsv(chans_df)
            with pytest.raises(Exception):
                writer.write_electrode_coords()
            with pytest.raises(Exception):
                writer.write_scans_tsv(scans_df)
            with pytest.raises(Exception):
                writer.write_sidecar_json(sidecar)

            writer.write_participants_json(participant_dict)
            writer.write_participants_tsv(participant_df)
示例#4
0
    def load_run(self, session_id, run_id, kind="eeg"):
        """
        Load in the raw data for a specific run.

        Parameters
        ----------
        session_id :
        run_id :
        kind :

        Returns
        -------
        mne.IO.BaseRaw
            An object containing the raw EEG data

        """
        loader = BidsLoader(bids_root=self.bids_root, bids_basename=None)
        raw = loader.load_dataset()
        return raw
示例#5
0
    def __init__(self, bids_root, bids_fname: str, verbose: bool = True):
        super(BidsRun, self).__init__(bids_root=bids_root)

        # ensures just base path
        self.bids_fname = os.path.basename(bids_fname)

        # what is the modality -- meg, eeg, ieeg to read
        self.bids_basename = "_".join(bids_fname.split("_")[:-1])
        self.kind = bids_fname.split("_")[-1].split(".")[0]

        # extract BIDS parameters from the bids filename to be loaded/modified
        # gets: subjectid, sessionid, acquisition type, task name, runid, file extension
        params = _parse_bids_filename(self.bids_fname, verbose=verbose)
        self.subject_id, self.session_id = params["sub"], params["ses"]
        self.acquisition, self.task, self.run = (
            params["acq"],
            params["task"],
            params["run"],
        )
        _, self.ext = _parse_ext(self.bids_fname)

        self.result_fpath = None

        # instantiate a loader/writer
        self.loader = BidsLoader(
            bids_root=self.bids_root,
            bids_basename=self.bids_basename,
            kind=self.kind,
            datatype=self.ext,
        )
        self.writer = BidsWriter(
            bids_root=self.bids_root,
            bids_basename=self.bids_basename,
            kind=self.kind,
            datatype=self.ext,
        )

        if not os.path.exists(self.loader.datafile_fpath):
            raise RuntimeError(
                f"Bids dataset run does not exist at {self.loader.datafile_fpath}. "
                f"Please first create the dataset in BIDS format."
            )
示例#6
0
    def __init__(self, bids_root, subject_id: str, verbose):
        super(BidsPatient, self).__init__(bids_root=bids_root)

        # what is the modality -- meg, eeg, ieeg to read
        self.bids_basename = make_bids_basename(subject=subject_id)

        # extract BIDS parameters from the bids filename to be loaded/modified
        self.subject_id = subject_id

        # instantiate a loader/writer
        self.loader = BidsLoader(
            bids_root=self.bids_root, bids_basename=self.bids_basename,
        )
        self.writer = BidsWriter(
            bids_root=self.bids_root, bids_basename=self.bids_basename,
        )

        # run to cache and search to get all datasets available for this patient
        self._get_all_datasets()

        # run BIDS compatability checks
        self._bids_compatible_check()
示例#7
0
    def preprocess_edf_files(self, filelist: list = None, line_noise=60, kind="eeg"):
        """
        Convert .edf files to .fif and .json file pair.

        Parameters
        ----------
        filelist : list[os.PathLike]
            The list of all files to be converted into .fif format. Default is all edf files available
        line_noise : int
            The line noise to be eliminated. Typically 60 Hz in America

        """
        if filelist is None:
            filelist = self._find_non_processed_files()
        for i, fpath in enumerate(filelist):
            if not os.path.exists(fpath):
                raise OSError(f"{fpath} doesn't exist. Please pass in valid filepaths.")
            if line_noise != 50:
                loader = BidsLoader(bids_root=self.bids_root, bids_basename=None)
                sidecardict = loader.load_sidecar_json()
                sidecardict["PowerLineFrequency"] = line_noise

                writer = BidsWriter(bids_root=self.bids_root, bids_basename=None)
                writer.write_sidecar_json(sidecardict)
示例#8
0
class BidsPatient(BaseBids):
    """
    Class for patient data that inherits basic dataset functionality and follows Bids format.

    The additional components for this are related to the fact that each Patient can
    have multiple EEGTimeSeries, or Results related to it.

    Attributes
    ----------
    bids_fname: str
        Identifier for the session
    bids_root: Union[str, os.PathLike]
        The base directory for the Bids dataset that this BidsPatient belongs
    managing_user: str
        Managing user that is tied to this Patient. E.g. a clinical center, or a specific researcher,
        or specific doctor.
    modality: str
        The type of recording the BidsPatient underwent during this session.

    """

    def __init__(self, bids_root, subject_id: str, verbose):
        super(BidsPatient, self).__init__(bids_root=bids_root)

        # what is the modality -- meg, eeg, ieeg to read
        self.bids_basename = make_bids_basename(subject=subject_id)

        # extract BIDS parameters from the bids filename to be loaded/modified
        self.subject_id = subject_id

        # instantiate a loader/writer
        self.loader = BidsLoader(
            bids_root=self.bids_root, bids_basename=self.bids_basename,
        )
        self.writer = BidsWriter(
            bids_root=self.bids_root, bids_basename=self.bids_basename,
        )

        # run to cache and search to get all datasets available for this patient
        self._get_all_datasets()

        # run BIDS compatability checks
        self._bids_compatible_check()

    def __str__(self):
        """
        Print the string representation of BidsPatient.

        Returns
        -------
        str
            A description of the BidsPatient including the number of datasets.

        """
        return "{} Patient with {} Dataset Recordings ".format(
            self.subject_id, self.numdatasets
        )

    def _bids_compatible_check(self):
        """
        Check that the BIDS patient has the necessary folders and files.

        Checks existence of files:
        - participant tsv/json files
        - scans tsv file

        Returns
        -------
        None

        """
        validator = bids.BIDSValidator(index_associated=True)

        valid_check = []
        filepaths = {
            "participant_tsv_fpath": self.loader.rel_participantstsv_fpath,
            "participant_json_fpath": self.loader.rel_participantsjson_fpath,
            "scans_tsv_fpath": self.loader.rel_scanstsv_fpath,
        }

        for bids_name, filepath in filepaths.items():
            if not filepath.startswith("/"):
                filepath = "/" + filepath
            _check = validator.is_bids(filepath)
            valid_check.append(_check)
            if _check is False:
                raise RuntimeError(
                    f"Bids patient {self.subject_id} does not have valid filepaths associated at "
                    f"{bids_name}: {filepath}."
                )

        if any(x is False for x in valid_check):
            raise RuntimeError(
                f"Bids patient {self.subject_id} does not have valid filepaths associated."
            )

    def _loader(self, runid):
        """
        Create a BidsLoader for a specific runid.

        Parameters
        ----------
        runid : str
            Identifier for the desired recording

        """
        # instantiate a loader/writer
        self.loader = BidsLoader(bids_root=self.bids_root, bids_basename=None)

    def _write(self, runid):
        """
        Create a BidsWriter for a specific runid.

        Parameters
        ----------
        runid : str
            Identifier for the desired recording

        """
        self.writer = BidsWriter(bids_root=self.bids_root, bids_basename=None)

    def _create_derivative_folders(self, types="all"):
        """
        Create the custom folders for a BidsPatient.

        Parameters
        ----------
        types : str
            Which derivative folders to create. Can be "all", "preprocess", or "results".

        """
        if types not in ["all", "preprocess", "results"]:
            raise ValueError(
                "Can only create two types of derivative folders right now."
            )

        # create preprocess, results folders
        BidsBuilder.make_preprocessed_folder(
            self.bids_root, self.subject_id, self.session_id
        )
        BidsBuilder.make_results_folder(
            self.bids_root, self.subject_id, self.session_id
        )

    def get_subject_sessions(self):
        """
        Find all of the sessions that the BidsPatient has.

        Returns
        -------
        The list of sessions

        """
        return mne_bids.utils.get_entity_vals(self.subjdir, "ses")

    def get_subject_runs(self):
        """
        Find all the runs that the BidsPatient has.

        Returns
        -------
        list[str]
            The list of runs

        """
        return mne_bids.utils.get_entity_vals(self.subjdir, "run")

    def get_subject_kinds(self):
        """
        Find all the types of recordings that the BidsPatient has.

        Returns
        -------
        list[str]
            The list of recording types

        """
        return mne_bids.utils.get_kinds(self.subjdir)

    @property
    def numdatasets(self):
        """
        Find the number of datasets that the BidsPatient has.

        Returns
        -------
        int
            The number of datasets

        """
        return len(self.get_subject_runs())

    def _get_all_datasets(self):
        """Load in all the dataset paths."""
        # Should update after creating a run, but does not work currently
        edffiles = self.query.get(extension=".edf", return_type="file")
        fiffiles = self.query.get(extension=self.ext, return_type="file")
        self.edf_fpaths = edffiles
        self.dataset_fpaths = fiffiles

    def load_subject_metadata(self) -> Dict:
        """
        Find all the metadata corresponding to this subject.

        Returns
        -------
        dict
            A dictionary containing metadata information

        """
        participants_json = self.loader.load_participants_json()
        participants_tsv = self.loader.load_participants_tsv()

        # json file describes columns for this patient
        metadata = participants_tsv[
            participants_tsv["participant_id"]
            == make_bids_basename(subject=self.subject_id)
        ].to_dict("r")[0]
        # metadata = participants_tsv.set_index('participant_id')[self.subject_val].to_dict(orient="index")
        metadata["field_descriptions"] = participants_json
        return metadata

    def load_session(self, session_id, kind="eeg", extension="edf"):
        """
        Load in the all runs for the session.

        Parameters
        ----------
        session_id :
        kind :
        extension :

        Returns
        -------
        list[mne.io.BaseRaw]
            A list of objects with raw EEG data for a single session

        """
        # query for the run ids in the session based on factors
        runs_in_session = self.query.get(
            target="run",
            subject=self.subject_id,
            session=self.session_id,
            return_type="id",
            datatype=kind,
            extension=extension,
        )

        # load in all the mne_raw datasets
        rawlist = []
        for run_id in runs_in_session:
            loader = BidsLoader(bids_root=self.bids_root, bids_basename=None)
            rawlist.append(loader.load_dataset())
        return rawlist

    def load_run(self, session_id, run_id, kind="eeg"):
        """
        Load in the raw data for a specific run.

        Parameters
        ----------
        session_id :
        run_id :
        kind :

        Returns
        -------
        mne.IO.BaseRaw
            An object containing the raw EEG data

        """
        loader = BidsLoader(bids_root=self.bids_root, bids_basename=None)
        raw = loader.load_dataset()
        return raw

    def preprocess_edf_files(self, filelist: list = None, line_noise=60, kind="eeg"):
        """
        Convert .edf files to .fif and .json file pair.

        Parameters
        ----------
        filelist : list[os.PathLike]
            The list of all files to be converted into .fif format. Default is all edf files available
        line_noise : int
            The line noise to be eliminated. Typically 60 Hz in America

        """
        if filelist is None:
            filelist = self._find_non_processed_files()
        for i, fpath in enumerate(filelist):
            if not os.path.exists(fpath):
                raise OSError(f"{fpath} doesn't exist. Please pass in valid filepaths.")
            if line_noise != 50:
                loader = BidsLoader(bids_root=self.bids_root, bids_basename=None)
                sidecardict = loader.load_sidecar_json()
                sidecardict["PowerLineFrequency"] = line_noise

                writer = BidsWriter(bids_root=self.bids_root, bids_basename=None)
                writer.write_sidecar_json(sidecardict)

            # TODO: Check if .fif file already exists. Otherwise convert to fif

    def _find_non_processed_files(self):
        """
        Find the .edf files that have not been preprocessed into .fif files.

        Returns
        -------
        list:
            File paths for unprocessed recordings.

        """
        scans_df = self.loader.load_scans_tsv()
        colnames = scans_df.columns
        if "original_filename" not in colnames:
            return scans_df["filename"].tolist()
        return scans_df[scans_df["original_filename"] == ""]["filename"].tolist()

    def get_filesizes(self):
        """
        Get the total file size of all the files attached for this patient.

        Returns
        -------
        int
            size of all files

        """
        size = 0
        for f in self.dataset_fpaths:
            size += os.path.getsize(f)
        return size

    def _load_metadata(self, metadata: dict):
        """
        Load the passed metadata into memory.

        Parameters
        ----------
        metadata :

        """
        self.metadata = metadata

    def _create_metadata(self):
        """
        Create the metadata dictionary from whatever is in the participants.tsv file.

        Returns
        -------
        dict
            A dictionary representation of the metadata.

        """
        participants_df = self.loader.load_participants_tsv()
        participants_series = participants_df.loc[
            participants_df["participant_id"]
            == make_bids_basename(subject=self.subject_id)
        ].squeeze()
        series_dict = {"participant_id": participants_series.name}
        for index, value in participants_series.iteritems():
            series_dict[index] = value
        return series_dict

    def _write_to_participants_tsv(self, participants_df):
        participants_df.reset_index(inplace=True)
        self.writer.write_participants_tsv(participants_df)

    def add_participants_field(
        self,
        column_id,
        description: Union[str, dict],
        subject_val: str = "n/a",
        default_val: str = "n/a",
    ):
        """
        Add a field to the participants files (tsv and json).

        Parameters
        ----------
        column_id : str
            The new column to add to the participants.tsv file
        description: Union[str, dict]
            The new column's description to be added to
        subject_val : str
            The value of column_id for this BidsPatient
        default_val :
            The value of column_id to fill for the rest of subjects in participants.tsv

        """
        # write to participants json
        participants_dict = self.loader.load_participants_json()
        participants_df = self.loader.load_participants_tsv()
        participants_df = participants_df.set_index("participant_id")
        if column_id not in participants_dict.keys():
            participants_dict = BidsUtils.add_field_to_participants_json(
                participants_dict, column_id, description
            )
            self.writer.write_participants_json(participants_dict)

        # write to participants tsv file
        colnames = participants_df.columns
        if column_id not in colnames:
            add_list = [default_val] * participants_df.shape[0]
            participants_df[column_id] = add_list
            if subject_val != default_val:
                participants_df.at[
                    make_bids_basename(subject=self.subject_id), column_id
                ] = subject_val
            self._write_to_participants_tsv(participants_df)
        else:
            warnings.warn(
                "Field already exists for the participants file. Modifying instead"
            )
            self.modify_participants_file(column_id, subject_val)

        metadata = self._create_metadata()
        self._load_metadata(metadata)

    def remove_participants_field(self, column_id: str) -> Dict:
        """
        Remove a field from the participants files (tsv and json).

        Parameters
        ----------
        column_id : str
            The name of the column to remove from the json and tsv files

        """
        # modify at the JSON level
        participants_dict = self.loader.load_participants_json()
        participants_df = self.loader.load_participants_tsv()
        participants_df = participants_df.set_index("participant_id")
        if column_id not in participants_dict.keys():
            raise LookupError(
                f"The field {column_id} does not exist in the participants file"
            )
        participants_dict.pop(column_id, None)
        self.writer.write_participants_json(participants_dict)

        # modify at the TSV level
        colnames = participants_df.columns
        if column_id not in colnames:
            raise ValueError(
                f"The field {column_id} does not exist in the participants file"
            )
        participants_df.drop(column_id, axis=1, inplace=True)
        self._write_to_participants_tsv(participants_df)

        # recreate metadata
        metadata = self._create_metadata()
        self._load_metadata(metadata)

    def modify_participants_file(self, column_id: str, new_value: str):
        """
        Modify the BIDS dataset participants tsv file to include a new value.

        Parameters
        ----------
        column_id : str
            The name of the column to which data should be modified. Must exist in the tsv file
        new_value : str
            The new value of the column_id for this BidsPatient

        """
        # load in the data and do error check and set index
        participants_df = self.loader.load_participants_tsv()
        participants_df = participants_df.set_index("participant_id")
        colnames = participants_df.columns
        if column_id not in colnames:
            raise ValueError(
                f"{column_id} not in the existing participants file. Add it first."
            )

        # modify at the TSV level and reset_index
        participants_df.at[
            make_bids_basename(subject=self.subject_id), column_id
        ] = new_value
        self._write_to_participants_tsv(participants_df)

        # recreate metadata
        metadata = self._create_metadata()
        self._load_metadata(metadata)

    def _create_participants_json(self, fname, overwrite=False, verbose=False):
        """
        Create the participants json file for Bids compliance. an Extension of what is present in MNE-BIDS.

        TODO:
        1. determine how to incorporate into MNE-BIDS

        Parameters
        ----------
        fname : Union[str, os.PathLike]
            The path of the participants json file
        overwrite : bool
            Whether to overwrite an existing participants json file
        verbose : bool
            Whether to print the data from the participants json file to stdout

        """
        cols = OrderedDict()
        cols["participant_id"] = {"Description": "Unique participant identifier"}
        cols["age"] = {
            "Description": "Age of the participant at time of testing",
            "Units": "years",
        }
        cols["sex"] = {
            "Description": "Biological sex of the participant",
            "Levels": {"F": "female", "M": "male"},
        }
        _write_json(fname, cols, overwrite, verbose)
示例#9
0
class BidsRun(BaseBids):
    """
    The class for a specific eeg snapshot recording that follows BIDS format.

    The additional functionality added by this class allow for addition, deletion, and modification
    of metadata corresponding the the recording channel annotation and the sidecar json.

    Attributes
    ----------
    bids_root: Union[str, os.PathLike]
        Where this dataset is located (base directory)

    bids_fname : str
        The base filename of the BIDS compatible files. Typically, this can be
        generated using make_bids_basename.
        Example: `sub-01_ses-01_task-testing_acq-01_run-01`.
        This will write the following files in the correct subfolder of the
        output_path::

            sub-01_ses-01_task-testing_acq-01_run-01_meg.fif
            sub-01_ses-01_task-testing_acq-01_run-01_meg.json
            sub-01_ses-01_task-testing_acq-01_run-01_channels.tsv
            sub-01_ses-01_task-testing_acq-01_run-01_coordsystem.json

        and the following one if events_data is not None::

            sub-01_ses-01_task-testing_acq-01_run-01_events.tsv

        and add a line to the following files::

            participants.tsv
            scans.tsv

        Note that the modality 'meg' is automatically inferred from the raw
        object and extension '.fif' is copied from raw.filenames.

    verbose: bool
        verbosity

    """

    def __init__(self, bids_root, bids_fname: str, verbose: bool = True):
        super(BidsRun, self).__init__(bids_root=bids_root)

        # ensures just base path
        self.bids_fname = os.path.basename(bids_fname)

        # what is the modality -- meg, eeg, ieeg to read
        self.bids_basename = "_".join(bids_fname.split("_")[:-1])
        self.kind = bids_fname.split("_")[-1].split(".")[0]

        # extract BIDS parameters from the bids filename to be loaded/modified
        # gets: subjectid, sessionid, acquisition type, task name, runid, file extension
        params = _parse_bids_filename(self.bids_fname, verbose=verbose)
        self.subject_id, self.session_id = params["sub"], params["ses"]
        self.acquisition, self.task, self.run = (
            params["acq"],
            params["task"],
            params["run"],
        )
        _, self.ext = _parse_ext(self.bids_fname)

        self.result_fpath = None

        # instantiate a loader/writer
        self.loader = BidsLoader(
            bids_root=self.bids_root,
            bids_basename=self.bids_basename,
            kind=self.kind,
            datatype=self.ext,
        )
        self.writer = BidsWriter(
            bids_root=self.bids_root,
            bids_basename=self.bids_basename,
            kind=self.kind,
            datatype=self.ext,
        )

        if not os.path.exists(self.loader.datafile_fpath):
            raise RuntimeError(
                f"Bids dataset run does not exist at {self.loader.datafile_fpath}. "
                f"Please first create the dataset in BIDS format."
            )

    def _create_bidsrun(self, data_fpath):
        """
        Create a BIDS Run if it does not exist.

        Parameters
        ----------
        data_fpath : str
            A filepath to the dataset, and it will create a BIDS-like run using MNE_Bids.

        """
        if pathlib.Path(data_fpath).suffix == ".fif":
            raw = mne.io.read_raw_fif(data_fpath)
        elif pathlib.Path(data_fpath).suffix == ".edf":
            raw = mne.io.read_raw_edf(data_fpath)
        else:
            raise RuntimeError(
                "Can't automatically create bids run using this extension. "
            )
        bids_basename = self.loader.datafile_fpath
        bids_dir = self.bids_root

        # copy over bids run to where it should be within the bids directory
        output_path = mne_bids.write_raw_bids(
            raw, bids_basename, output_path=bids_dir, overwrite=True, verbose=False
        )
        return output_path

    @property
    def fpath(self):
        """
        Get the location of the run's data file in this directory.

        Returns
        -------
        The path to the data file

        """
        return self.loader.datafile_fpath

    @property
    def sfreq(self):
        """
        Get the sampling frequency used in this recording.

        Returns
        -------
        The sampling frequency in HZ

        """
        sidecar = self.loader.load_sidecar_json()
        return sidecar["SamplingFrequency"]

    @property
    def linefreq(self):
        """
        Get the power line frequency used in this recording (i.e. either 50 Hz or 60 Hz).

        Returns
        -------
        The power line frequency.

        """
        sidecar = self.loader.load_sidecar_json()
        return sidecar["PowerLineFrequency"]

    @property
    def chs(self):
        """
        Get the channel labels as a numpy array.

        Returns
        -------
        A list of channel names in the recording.

        """
        chdf = self.loader.load_channels_tsv()
        return chdf["name"].to_numpy()

    def load_data(self):
        """
        Load the dataset using BidsLoader.

        Returns
        -------
        The mne.io.Raw dataset

        """
        return self.loader.load_dataset()

    def get_run_metadata(self):
        """
        Load the sidecar json file as a dict object.

        Returns
        -------
        The dictionary of metadata

        """
        return self.loader.load_sidecar_json()

    def get_channels_metadata(self):
        """
        Load the channel metadata from the tsv file as a dict object.

        Returns
        -------
        The dictionary of channel annotations.

        """
        chdf = self.loader.load_channels_tsv()
        return chdf.to_dict()

    def get_channel_types(self):
        """
        Return the channel types of this BidsRun as a pandas DataFrame.

        The name is the index and the type is the column value. Can easily convert to
        key, value pair of a dict if desired.

        Returns
        -------
        A pandas DataFrame containing the name and type of each channel.

        """
        chdf = self.loader.load_channels_tsv()
        return chdf[["name", "type"]].set_index("name")

    def get_channels_status(self):
        """
        Return the channel status of this BidsRun as a pandas DataFrame.

        The name is the index and the status is the column value. Can easily convert to
        key, value pair of a dict if desired.

        Returns
        -------
        A pandas DataFrame containing the name and status of each channel.

        """
        chdf = self.loader.load_channels_tsv()
        return chdf[["name", "status"]].set_index("name")

    def delete_sidecar_field(self, key):
        """
        Remove a field from the sidecar json file for this run.

        Parameters
        ----------
        key : str
            Which field to remove.

        Returns
        -------
        The modified dictionary

        """
        sidecar_dict = self.loader.load_sidecar_json()
        sidecar_dict.pop(key, None)
        self.writer.write_sidecar_json(sidecar_dict)
        return sidecar_dict

    def modify_sidecar_field(self, key, value, overwrite=False):
        """
        Modify the sidecar json to have the new value in the passed field.

        It will overwrite existing sidecar elements if already present. However, if overwrite is False,
        then it should raise a RunTimeError.


        Parameters
        ----------
        key : str
            Which field to modify
        value : str
            The new value for the passed key

        Returns
        -------
        The modified dictionary

        """
        # always make keys capitalized in sidecar json
        key = key[0].upper() + key[1:]

        sidecar_dict = self.loader.load_sidecar_json()
        current_keys = sidecar_dict.keys()
        if key in current_keys and overwrite is False:
            raise RuntimeError(
                f"The passed key {key} already exists in the sidecar json file. Please pass overwrite"
                f"as true if you wish to make the change"
            )
        sidecar_dict[key] = value
        self.writer.write_sidecar_json(sidecar_dict)

    def add_sidecar_field(self, key, value):
        """
        Add a field (i.e. key/value) to the sidecar json file.

        Parameters
        ----------
        key :
        value :

        Returns
        -------
        None
        """
        pass

    def append_channel_info(
        self, column_id: str, channel_id: str, value, channel_dict: Dict = None,
    ):
        """
        Add a new column to the channels tsv file.

        Parameters
        ----------
        column_id : str
            The name of the column to add
        channel_id : str
            The name of the singular channel to modify.
        value :
            The new value for the channel_id.
        channel_dict : Dict
            A dictionary of names and values for the new column

        """
        if value is not None and channel_dict is not None:
            raise TypeError(
                "Passed in both value and channel dictionary. Only pass in one!"
            )
        channel_df = self.loader.load_channels_tsv()
        colnames = channel_df.columns
        if column_id in colnames:
            raise RuntimeError(
                f"Already added in the column: {column_id}. Call modify_channel_info to"
                f"modify."
            )

        if channel_dict is not None:
            new_data = []
            for key, value in channel_dict.items():
                new_data.append(value)
        else:
            new_data = ["N/A"] * channel_df.shape[0]
        channel_df[column_id] = new_data

        channel_df.loc[channel_df["name"] == channel_id, column_id] = value

        self.writer.write_channels_tsv(channel_df)

    def modify_channel_info(self, column_id: str, channel_id: str, value=None):
        """
        Modify some information about a single recording channel.

        Parameters
        ----------
        column_id : str
            The column to modify. Must already exist in the tsv file
        channel_id : str
            The channel to modify. Must exist for the recording
        value :
            The value to add for the channel and column

        """
        channel_df = self.loader.load_channels_tsv()
        if column_id not in channel_df.columns:
            raise ValueError(
                f"Column id {column_id} not in the channels tsv file. Please pass a correct column or "
                f"add to the tsv file"
            )
        if channel_id not in channel_df["name"].tolist():
            raise LookupError(
                f"Passed channel {channel_id} not a valid channel for this recording."
            )
        channel_df.loc[channel_df["name"] == channel_id, column_id] = value
        self.writer.write_channels_tsv(channel_df)

    def delete_channel_info(self, column_id: str):
        """
        Remove column from the channels.tsv file.

        Parameters
        ----------
        column_id : (str)

        Returns
        -------
        None

        """
        channel_df = self.loader.load_channels_tsv()
        if column_id not in channel_df.columns:
            raise ValueError(
                f"Column id {column_id} not in the channels tsv file. Please pass a correct column or "
                f"add to the tsv file"
            )
        print("Inside delete: ", channel_df)
        channel_df.drop(columns=column_id, axis=1, inplace=True)
        print("Inside delete: ", channel_df)
        self.writer.write_channels_tsv(channel_df)

    def _update_sidecar_json_channelcounts(self):
        """
        Update sidecar json for channel counts.

        Reads the current tsv file for the run's channels and updates the sidecar json with the channel type.

        Should be called every time we modify anything in the sidecar json file.

        """
        # load in channel tsv file as a dataframe
        channels_data = self.loader.load_channels_tsv()
        types = channels_data["type"]

        # load in sidecar json as a dictionary
        sidecar_dict = self.loader.load_sidecar_json()
        numbers = types.value_counts()

        # go through each possible electrode type and update the count in sidecar json
        for chtype, count in zip(types, numbers):
            sidecar_dict[f"{chtype}ChannelCount"] = int(count)

        self.writer.write_sidecar_json(sidecar_dict)