diff --git a/cmlreaders/cmlreader.py b/cmlreaders/cmlreader.py
index 485f9e2..7380def 100644
--- a/cmlreaders/cmlreader.py
+++ b/cmlreaders/cmlreader.py
@@ -1,8 +1,11 @@
 from typing import List, Tuple, Optional
+
+import numpy as np
 import pandas as pd
 
 from . import readers
-from .exc import IncompatibleParametersError
+from .data_index import get_data_index
+from .exc import IncompatibleParametersError, UnknownProtocolError
 from .util import get_root_dir
 
 
@@ -28,22 +31,97 @@ class CMLReader(object):
 
     """
     reader_names = {}
+    _index = None  # type: pd.DataFrame
 
-    def __init__(self, subject: Optional[str] =None,
+    def __init__(self, subject: str,
                  experiment: Optional[str] = None,
                  session: Optional[int] = None,
-                 localization: Optional[int] = 0,
-                 montage: Optional[int] = 0,
+                 localization: Optional[int] = None,
+                 montage: Optional[int] = None,
                  rootdir: Optional[str] = None):
 
         self.subject = subject
         self.experiment = experiment
         self.session = session
-        self.localization = localization
-        self.montage = montage
         self.rootdir = get_root_dir(rootdir)
+
+        self._localization = localization
+        self._montage = montage
+
+        self.protocol = self._get_protocol(self.subject)
+
         self.readers = {k: getattr(readers, v) for k, v in self.reader_names.items()}
 
+    def _load_index(self):
+        """Loads the data index. Used internally to determine montage and
+        localization nubmers.
+
+        """
+        if CMLReader._index is None:
+            CMLReader._index = get_data_index(self.protocol, rootdir=self.rootdir)
+
+            # Some subjects don't explicitly specify localization/montage
+            # numbers in the index, so they appear as NaNs.
+            CMLReader._index.montage.replace({np.nan: "0"}, inplace=True)
+            CMLReader._index.localization.replace({np.nan: "0"}, inplace=True)
+
+    @staticmethod
+    def _get_protocol(subject: str) -> str:
+        """Get the protocol name from the subject code.
+
+        This returns the ``<protocol> `` in ``/protocols/<protocol>``. For
+        example, it returns ``"r1"`` for RAM subjects.
+
+        """
+        if subject.startswith("R1"):
+            return "r1"
+        elif subject.startswith("LTP"):
+            return "ltp"
+        else:
+            raise UnknownProtocolError(
+                "Can't determine protocol for subject id " + subject
+            )
+
+    def _determine_localization_or_montage(self, which: str) -> Optional[int]:
+        """Inner workings of localization/montage properties.
+
+        Returns
+        -------
+        Montage or localization number if all are the same, otherwise None.
+
+        """
+        if which not in ["localization", "montage"]:
+            raise ValueError
+
+        self._load_index()
+
+        df = CMLReader._index[CMLReader._index["subject"] == self.subject]
+
+        if self.experiment is not None:
+            df = df[df.experiment == self.experiment]
+
+        if self.session is not None:
+            df = df[df.session == self.session]
+
+        if len(df[which].unique()) != 1:
+            return None
+        else:
+            return int(df[which].unique()[0])
+
+    @property
+    def localization(self) -> int:
+        """Determine the localization number."""
+        if self._localization is not None:
+            return self._localization
+        return self._determine_localization_or_montage("localization")
+
+    @property
+    def montage(self) -> int:
+        """Determine the montage number."""
+        if self._montage is not None:
+            return self._montage
+        return self._determine_localization_or_montage("montage")
+
     def get_reader(self, data_type, file_path=None):
         """ Return an instance of the reader class for the given data type """
 
diff --git a/cmlreaders/exc.py b/cmlreaders/exc.py
index f07509a..63a1a58 100644
--- a/cmlreaders/exc.py
+++ b/cmlreaders/exc.py
@@ -7,7 +7,14 @@ class UnsupportedOutputFormat(CMLReaderException):
 
 
 class UnsupportedRepresentation(CMLReaderException):
-    """ Raised when a data loading method is not supported for the given data type """
+    """Raised when a data loading method is not supported for the given data
+    type.
+
+    """
+
+
+class UnknownProtocolError(CMLReaderException):
+    """Raised when an unknown protocol (e.g., "r2") is found."""
 
 
 class MissingParameter(CMLReaderException):
diff --git a/cmlreaders/readers/eeg.py b/cmlreaders/readers/eeg.py
index cb18e3f..f801247 100644
--- a/cmlreaders/readers/eeg.py
+++ b/cmlreaders/readers/eeg.py
@@ -289,11 +289,11 @@ class RamulatorHDF5Reader(BaseEEGReader):
         else:
             with h5py.File(self.filename, 'r') as hfile:
                 bpinfo = hfile['bipolar_info']
-                all_nums = [(int(a), int(b))for (a, b) in
-                            list(zip(bpinfo['ch0_label'][()],
-                                     bpinfo['ch1_label'][()])
-                                 )
-                            ]
+                all_nums = [
+                    (int(a), int(b)) for (a, b) in list(
+                        zip(bpinfo['ch0_label'][()], bpinfo['ch1_label'][()])
+                    )
+                ]
             scheme_nums = list(zip(scheme['contact_1'], scheme['contact_2']))
             is_valid_channel = [channel in all_nums for channel in scheme_nums]
 

Automatically determine localization and montage numbers
Rather than defaulting to 0, `CMLReader` should read the data index (this can be cached to avoid re-reading several times) and determine localization and montage number from there when `session` is specified. In cases where either is `nan`, 0 can be assumed.
diff --git a/cmlreaders/test/test_cmlreader.py b/cmlreaders/test/test_cmlreader.py
index d420373..6f2476a 100644
--- a/cmlreaders/test/test_cmlreader.py
+++ b/cmlreaders/test/test_cmlreader.py
@@ -1,14 +1,32 @@
 import functools
 import os
+
 from pkg_resources import resource_filename
 import pytest
 
 from cmlreaders import CMLReader
+from cmlreaders.test.utils import patched_cmlreader
 
 datafile = functools.partial(resource_filename, 'cmlreaders.test.data')
 
 
 class TestCMLReader:
+    @pytest.mark.parametrize("subject,experiment,session,localization,montage", [
+        ("R1278E", "catFR1", 0, 0, 1),
+        ("R1278E", "catFR1", None, 0, 1),
+        ("R1278E", "PAL1", None, 2, 2),
+        ("R1278E", "PAL3", 2, 2, 2),
+        ("R1278E", "TH1", 0, 0, 0),
+        ("R1278E", "TH1", None, 0, 0),
+    ])
+    def test_determine_localization_or_montage(self, subject, experiment,
+                                               session, localization, montage):
+        with patched_cmlreader():
+            reader = CMLReader(subject=subject, experiment=experiment,
+                               session=session)
+            assert reader.montage == montage
+            assert reader.localization == localization
+
     @pytest.mark.rhino
     @pytest.mark.parametrize("file_type", [
         'voxel_coordinates', 'classifier_excluded_leads', 'jacksheet',
@@ -22,7 +40,7 @@ class TestCMLReader:
         subject = "R1405E"
         experiment = "FR1"
         session = 1
-        localization = 0
+        localization = 1  # session 1 used localization 1, not 0
 
         if file_type in ["electrode_categories", "classifier_summary",
                          "math_summary", "session_summary",
diff --git a/cmlreaders/test/test_eeg.py b/cmlreaders/test/test_eeg.py
index 35b45f6..9f85426 100644
--- a/cmlreaders/test/test_eeg.py
+++ b/cmlreaders/test/test_eeg.py
@@ -1,5 +1,6 @@
 import json
 from pathlib import Path
+
 from pkg_resources import resource_filename
 import pytest
 
@@ -15,14 +16,16 @@ from cmlreaders.readers.eeg import (
     SplitEEGReader,
 )
 from cmlreaders.readers import MontageReader
+from cmlreaders.test.utils import patched_cmlreader
 
 
 @pytest.fixture
 def events():
-    cml_reader = CMLReader()
-    path = resource_filename('cmlreaders.test.data', 'all_events.json')
-    reader = cml_reader.get_reader('events', file_path=path)
-    return reader.as_dataframe()
+    with patched_cmlreader():
+        cml_reader = CMLReader("R1389J")
+        path = resource_filename('cmlreaders.test.data', 'all_events.json')
+        reader = cml_reader.get_reader('events', file_path=path)
+        return reader.as_dataframe()
 
 
 @pytest.mark.parametrize("millis,rate,samples", [
diff --git a/cmlreaders/test/utils.py b/cmlreaders/test/utils.py
new file mode 100644
index 0000000..e062c90
--- /dev/null
+++ b/cmlreaders/test/utils.py
@@ -0,0 +1,17 @@
+from contextlib import contextmanager
+from unittest.mock import patch
+
+from pkg_resources import resource_filename
+
+from cmlreaders import CMLReader
+from cmlreaders.data_index import _index_dict_to_dataframe, read_index
+
+
+@contextmanager
+def patched_cmlreader():
+    """Patches CMLReader to load the data index locally."""
+    raw = read_index(resource_filename("cmlreaders.test.data", "r1.json"))
+
+    with patch.object(CMLReader, "_load_index",
+                      side_effect=setattr(CMLReader, "_index", _index_dict_to_dataframe(raw))):
+        yield

pip install -e .
parse_log_pytest
requirements.txt
pytest pytest-cov
apt-get update apt-get install -y gcc
3.6
requirements.txt
pytest --no-header -rA --tb=line --color=no -p no:cacheprovider -W ignore::DeprecationWarning
alabaster==0.7.13
async-generator==1.10
attrs==22.2.0
Babel==2.11.0
bleach==4.1.0
cached-property==1.5.2
certifi==2021.5.30
charset-normalizer==2.0.12
-e git+https://github.com/pennmem/cmlreaders.git@3646cf5f4b10fa708f3fc07cdab70b10e93b5b0a#egg=cmlreaders
codecov==2.1.13
coverage==6.2
decorator==5.1.1
defusedxml==0.7.1
docutils==0.18.1
entrypoints==0.4
flake8==3.9.2
h5py==3.1.0
idna==3.10
imagesize==1.4.1
importlib-metadata==4.8.3
iniconfig==1.1.1
ipython-genutils==0.2.0
Jinja2==3.0.3
jsonschema==3.2.0
jupyter-client==7.1.2
jupyter-core==4.9.2
jupyterlab-pygments==0.1.2
MarkupSafe==2.0.1
mccabe==0.6.1
mistune==0.8.4
mne==0.23.4
nbclient==0.5.9
nbconvert==6.0.7
nbformat==5.1.3
nbsphinx==0.8.8
nest-asyncio==1.6.0
numpy==1.19.5
packaging==21.3
pandas==1.1.5
pandocfilters==1.5.1
pluggy==1.0.0
py==1.11.0
pycodestyle==2.7.0
pyflakes==2.3.1
Pygments==2.14.0
pyparsing==3.1.4
pyrsistent==0.18.0
pytest==7.0.1
pytest-cov==4.0.0
python-dateutil==2.9.0.post0
pytz==2025.2
pyzmq==25.1.2
requests==2.27.1
scipy==1.5.4
six==1.17.0
snowballstemmer==2.2.0
Sphinx==5.3.0
sphinx-rtd-theme==2.0.0
sphinxcontrib-applehelp==1.0.2
sphinxcontrib-devhelp==1.0.2
sphinxcontrib-htmlhelp==2.0.0
sphinxcontrib-jquery==4.1
sphinxcontrib-jsmath==1.0.1
sphinxcontrib-qthelp==1.0.3
sphinxcontrib-serializinghtml==1.1.5
testpath==0.6.0
tomli==1.2.3
tornado==6.1
traitlets==4.3.3
typing_extensions==4.1.1
urllib3==1.26.20
webencodings==0.5.1
zipp==3.6.0

name: cmlreaders
channels:
  - defaults
  - https://repo.anaconda.com/pkgs/main
  - https://repo.anaconda.com/pkgs/r
  - conda-forge
dependencies:
  - _libgcc_mutex=0.1=main
  - _openmp_mutex=5.1=1_gnu
  - ca-certificates=2025.2.25=h06a4308_0
  - certifi=2021.5.30=py36h06a4308_0
  - ld_impl_linux-64=2.40=h12ee557_0
  - libffi=3.3=he6710b0_2
  - libgcc-ng=11.2.0=h1234567_1
  - libgomp=11.2.0=h1234567_1
  - libstdcxx-ng=11.2.0=h1234567_1
  - ncurses=6.4=h6a678d5_0
  - openssl=1.1.1w=h7f8727e_0
  - pip=21.2.2=py36h06a4308_0
  - python=3.6.13=h12debd9_1
  - readline=8.2=h5eee18b_0
  - setuptools=58.0.4=py36h06a4308_0
  - sqlite=3.45.3=h5eee18b_0
  - tk=8.6.14=h39e8969_0
  - wheel=0.37.1=pyhd3eb1b0_0
  - xz=5.6.4=h5eee18b_1
  - zlib=1.2.13=h5eee18b_1
  - pip:
      - alabaster==0.7.13
      - async-generator==1.10
      - attrs==22.2.0
      - babel==2.11.0
      - bleach==4.1.0
      - cached-property==1.5.2
      - charset-normalizer==2.0.12
      - codecov==2.1.13
      - coverage==6.2
      - decorator==5.1.1
      - defusedxml==0.7.1
      - docutils==0.18.1
      - entrypoints==0.4
      - flake8==3.9.2
      - h5py==3.1.0
      - idna==3.10
      - imagesize==1.4.1
      - importlib-metadata==4.8.3
      - iniconfig==1.1.1
      - ipython-genutils==0.2.0
      - jinja2==3.0.3
      - jsonschema==3.2.0
      - jupyter-client==7.1.2
      - jupyter-core==4.9.2
      - jupyterlab-pygments==0.1.2
      - markupsafe==2.0.1
      - mccabe==0.6.1
      - mistune==0.8.4
      - mne==0.23.4
      - nbclient==0.5.9
      - nbconvert==6.0.7
      - nbformat==5.1.3
      - nbsphinx==0.8.8
      - nest-asyncio==1.6.0
      - numpy==1.19.5
      - packaging==21.3
      - pandas==1.1.5
      - pandocfilters==1.5.1
      - pluggy==1.0.0
      - py==1.11.0
      - pycodestyle==2.7.0
      - pyflakes==2.3.1
      - pygments==2.14.0
      - pyparsing==3.1.4
      - pyrsistent==0.18.0
      - pytest==7.0.1
      - pytest-cov==4.0.0
      - python-dateutil==2.9.0.post0
      - pytz==2025.2
      - pyzmq==25.1.2
      - requests==2.27.1
      - scipy==1.5.4
      - six==1.17.0
      - snowballstemmer==2.2.0
      - sphinx==5.3.0
      - sphinx-rtd-theme==2.0.0
      - sphinxcontrib-applehelp==1.0.2
      - sphinxcontrib-devhelp==1.0.2
      - sphinxcontrib-htmlhelp==2.0.0
      - sphinxcontrib-jquery==4.1
      - sphinxcontrib-jsmath==1.0.1
      - sphinxcontrib-qthelp==1.0.3
      - sphinxcontrib-serializinghtml==1.1.5
      - testpath==0.6.0
      - tomli==1.2.3
      - tornado==6.1
      - traitlets==4.3.3
      - typing-extensions==4.1.1
      - urllib3==1.26.20
      - webencodings==0.5.1
      - zipp==3.6.0
prefix: /opt/conda/envs/cmlreaders

cmlreaders/test/test_cmlreader.py::TestCMLReader::test_determine_localization_or_montage[R1278E-catFR1-0-0-1] cmlreaders/test/test_cmlreader.py::TestCMLReader::test_determine_localization_or_montage[R1278E-catFR1-None-0-1] cmlreaders/test/test_cmlreader.py::TestCMLReader::test_determine_localization_or_montage[R1278E-PAL1-None-2-2] cmlreaders/test/test_cmlreader.py::TestCMLReader::test_determine_localization_or_montage[R1278E-PAL3-2-2-2] cmlreaders/test/test_cmlreader.py::TestCMLReader::test_determine_localization_or_montage[R1278E-TH1-0-0-0] cmlreaders/test/test_cmlreader.py::TestCMLReader::test_determine_localization_or_montage[R1278E-TH1-None-0-0] cmlreaders/test/test_eeg.py::test_events_to_epochs[100--100] cmlreaders/test/test_eeg.py::test_events_to_epochs[100-0] cmlreaders/test/test_eeg.py::test_events_to_epochs[500--100] cmlreaders/test/test_eeg.py::test_events_to_epochs[500-0]
cmlreaders/test/test_cmlreader.py::TestCMLReader::test_load_from_rhino[voxel_coordinates] cmlreaders/test/test_cmlreader.py::TestCMLReader::test_load_from_rhino[classifier_excluded_leads] cmlreaders/test/test_cmlreader.py::TestCMLReader::test_load_from_rhino[jacksheet] cmlreaders/test/test_cmlreader.py::TestCMLReader::test_load_from_rhino[good_leads] cmlreaders/test/test_cmlreader.py::TestCMLReader::test_load_from_rhino[leads] cmlreaders/test/test_cmlreader.py::TestCMLReader::test_load_from_rhino[electrode_coordinates] cmlreaders/test/test_cmlreader.py::TestCMLReader::test_load_from_rhino[prior_stim_results] cmlreaders/test/test_cmlreader.py::TestCMLReader::test_load_from_rhino[target_selection_table] cmlreaders/test/test_cmlreader.py::TestCMLReader::test_load_from_rhino[electrode_categories] cmlreaders/test/test_cmlreader.py::TestCMLReader::test_load_from_rhino[classifier_summary] cmlreaders/test/test_cmlreader.py::TestCMLReader::test_load_from_rhino[math_summary] cmlreaders/test/test_cmlreader.py::TestCMLReader::test_load_from_rhino[session_summary] cmlreaders/test/test_cmlreader.py::TestCMLReader::test_load_from_rhino[pairs] cmlreaders/test/test_cmlreader.py::TestCMLReader::test_load_from_rhino[contacts] cmlreaders/test/test_cmlreader.py::TestCMLReader::test_load_from_rhino[localization] cmlreaders/test/test_cmlreader.py::TestCMLReader::test_load_from_rhino[baseline_classifier] cmlreaders/test/test_cmlreader.py::TestCMLReader::test_load_from_rhino[used_classifier] cmlreaders/test/test_cmlreader.py::TestCMLReader::test_load_from_rhino[events] cmlreaders/test/test_cmlreader.py::TestCMLReader::test_load_from_rhino[all_events] cmlreaders/test/test_cmlreader.py::TestCMLReader::test_load_from_rhino[task_events] cmlreaders/test/test_cmlreader.py::TestCMLReader::test_load_from_rhino[math_events] cmlreaders/test/test_cmlreader.py::TestCMLReader::test_load[classifier_summary.h5] cmlreaders/test/test_cmlreader.py::TestCMLReader::test_load[math_summary.h5] cmlreaders/test/test_cmlreader.py::TestCMLReader::test_load[session_summary.h5] cmlreaders/test/test_cmlreader.py::TestCMLReader::test_load[baseline_classifier.zip] cmlreaders/test/test_cmlreader.py::TestCMLReader::test_load[used_classifier.zip] cmlreaders/test/test_cmlreader.py::TestCMLReader::test_get_reader[classifier_summary] cmlreaders/test/test_cmlreader.py::TestCMLReader::test_get_reader[math_summary] cmlreaders/test/test_cmlreader.py::TestCMLReader::test_get_reader[session_summary] cmlreaders/test/test_cmlreader.py::TestCMLReader::test_get_reader[baseline_classifier] cmlreaders/test/test_cmlreader.py::TestCMLReader::test_get_reader[used_classifier] cmlreaders/test/test_cmlreader.py::TestCMLReader::test_ps4_events[R1354E-PS4_FR-1] cmlreaders/test/test_eeg.py::TestFileReaders::test_split_eeg_reader cmlreaders/test/test_eeg.py::TestFileReaders::test_split_eeg_reader_missing_contacts cmlreaders/test/test_eeg.py::TestFileReaders::test_ramulator_hdf5_reader_rhino[R1363T-FR1-0-178-True] cmlreaders/test/test_eeg.py::TestFileReaders::test_ramulator_hdf5_reader_rhino[R1392N-PAL1-0-112-False] cmlreaders/test/test_eeg.py::TestEEGReader::test_eeg_reader[R1298E] cmlreaders/test/test_eeg.py::TestEEGReader::test_eeg_reader[R1387E] cmlreaders/test/test_eeg.py::TestEEGReader::test_eeg_reader_with_events[R1161E] cmlreaders/test/test_eeg.py::TestEEGReader::test_eeg_reader_with_events[R1387E] cmlreaders/test/test_eeg.py::TestEEGReader::test_rereference[R1384J-False] cmlreaders/test/test_eeg.py::TestEEGReader::test_rereference[R1111M-True]
cmlreaders/test/test_cmlreader.py::TestCMLReader::test_load[voxel_coordinates.txt] cmlreaders/test/test_cmlreader.py::TestCMLReader::test_load[classifier_excluded_leads.txt] cmlreaders/test/test_cmlreader.py::TestCMLReader::test_load[jacksheet.txt] cmlreaders/test/test_cmlreader.py::TestCMLReader::test_load[good_leads.txt] cmlreaders/test/test_cmlreader.py::TestCMLReader::test_load[leads.txt] cmlreaders/test/test_cmlreader.py::TestCMLReader::test_load[electrode_coordinates.csv] cmlreaders/test/test_cmlreader.py::TestCMLReader::test_load[prior_stim_results.csv] cmlreaders/test/test_cmlreader.py::TestCMLReader::test_load[target_selection_table.csv] cmlreaders/test/test_cmlreader.py::TestCMLReader::test_load[pairs.json] cmlreaders/test/test_cmlreader.py::TestCMLReader::test_load[contacts.json] cmlreaders/test/test_cmlreader.py::TestCMLReader::test_load[localization.json] cmlreaders/test/test_cmlreader.py::TestCMLReader::test_load[all_events.json] cmlreaders/test/test_cmlreader.py::TestCMLReader::test_load[math_events.json] cmlreaders/test/test_cmlreader.py::TestCMLReader::test_load[task_events.json] cmlreaders/test/test_cmlreader.py::TestCMLReader::test_get_reader[voxel_coordinates] cmlreaders/test/test_cmlreader.py::TestCMLReader::test_get_reader[classifier_excluded_leads] cmlreaders/test/test_cmlreader.py::TestCMLReader::test_get_reader[jacksheet] cmlreaders/test/test_cmlreader.py::TestCMLReader::test_get_reader[good_leads] cmlreaders/test/test_cmlreader.py::TestCMLReader::test_get_reader[leads] cmlreaders/test/test_cmlreader.py::TestCMLReader::test_get_reader[electrode_coordinates] cmlreaders/test/test_cmlreader.py::TestCMLReader::test_get_reader[prior_stim_results] cmlreaders/test/test_cmlreader.py::TestCMLReader::test_get_reader[target_selection_table] cmlreaders/test/test_cmlreader.py::TestCMLReader::test_get_reader[pairs] cmlreaders/test/test_cmlreader.py::TestCMLReader::test_get_reader[contacts] cmlreaders/test/test_cmlreader.py::TestCMLReader::test_get_reader[localization] cmlreaders/test/test_cmlreader.py::TestCMLReader::test_get_reader[all_events] cmlreaders/test/test_cmlreader.py::TestCMLReader::test_get_reader[math_events] cmlreaders/test/test_cmlreader.py::TestCMLReader::test_get_reader[task_events] cmlreaders/test/test_cmlreader.py::TestCMLReader::test_load_unimplemented cmlreaders/test/test_eeg.py::test_milliseconds_to_samples[1000-1000-1000] cmlreaders/test/test_eeg.py::test_milliseconds_to_samples[1000-500-500] cmlreaders/test/test_eeg.py::test_milliseconds_to_samples[1000-250-250] cmlreaders/test/test_eeg.py::test_samples_to_milliseconds[1000-1000-1000] cmlreaders/test/test_eeg.py::test_samples_to_milliseconds[1000-500-2000] cmlreaders/test/test_eeg.py::test_samples_to_milliseconds[1000-250-4000] cmlreaders/test/test_eeg.py::test_milliseconds_to_events[onsets0-1000-expected0] cmlreaders/test/test_eeg.py::test_milliseconds_to_events[onsets1-100-expected1] cmlreaders/test/test_eeg.py::test_events_to_epochs_simple cmlreaders/test/test_eeg.py::TestFileReaders::test_npy_reader cmlreaders/test/test_eeg.py::TestFileReaders::test_ramulator_hdf5_reader cmlreaders/test/test_eeg.py::TestFileReaders::test_ramulator_hdf5_rereference
cmlreaders/readers/eeg.py cmlreaders/cmlreader.py cmlreaders/exc.py
cmlreaders/readers/eeg.py cmlreaders/cmlreader.py cmlreaders/exc.py