Repository: mne-tools/mne-realtime

Issue:
mne_realtime.RtEpochs always returning an AssertionError
Using one of the examples provided by your package and my own code I cant get working the RtEpochs function.
Here is a code sample taken from the examples that you prvoide (https://mne.tools/mne-realtime/auto_examples/plot_compute_rt_decoder.html#sphx-glr-auto-examples-plot-compute-rt-decoder-py)

```Python
# Authors: Mainak Jas <mainak@neuro.hut.fi>
#
# License: BSD (3-clause)

import numpy as np
import matplotlib.pyplot as plt

from sklearn import preprocessing
from sklearn.linear_model import LogisticRegression
from sklearn.pipeline import Pipeline
from sklearn.model_selection import cross_val_score, ShuffleSplit
from mne.decoding import Vectorizer, FilterEstimator

import mne
from mne.datasets import sample

from mne_realtime import MockRtClient, RtEpochs

print(__doc__)

# Fiff file to simulate the realtime client
data_path = sample.data_path()
raw_fname = data_path + '/MEG/sample/sample_audvis_filt-0-40_raw.fif'
raw = mne.io.read_raw_fif(raw_fname, preload=True)

tmin, tmax = -0.2, 0.5
event_id = dict(aud_l=1, vis_l=3)

tr_percent = 60  # Training percentage
min_trials = 10  # minimum trials after which decoding should start

# select gradiometers
picks = mne.pick_types(raw.info, meg='grad', eeg=False, eog=True,
                       stim=True, exclude=raw.info['bads'])

# create the mock-client object
rt_client = MockRtClient(raw)

# create the real-time epochs object
rt_epochs = RtEpochs(rt_client, event_id, tmin, tmax, picks=picks, decim=1,
                     reject=dict(grad=4000e-13, eog=150e-6), baseline=None,
                     isi_max=4.)

# start the acquisition
rt_epochs.start()

# send raw buffers
rt_client.send_data(rt_epochs, picks, tmin=0, tmax=90, buffer_size=1000)

# Decoding in sensor space using a linear SVM
n_times = len(rt_epochs.times)

scores_x, scores, std_scores = [], [], []

# don't highpass filter because it's epoched data and the signal length
# is small
filt = FilterEstimator(rt_epochs.info, None, 40, fir_design='firwin')
scaler = preprocessing.StandardScaler()
vectorizer = Vectorizer()
clf = LogisticRegression(solver='lbfgs')

concat_classifier = Pipeline([('filter', filt), ('vector', vectorizer),
                              ('scaler', scaler), ('svm', clf)])

data_picks = mne.pick_types(rt_epochs.info, meg='grad', eeg=False, eog=False,
                            stim=False, exclude=raw.info['bads'])
ax = plt.subplot(111)
ax.set_xlabel('Trials')
ax.set_ylabel('Classification score (% correct)')
ax.set_title('Real-time decoding')
ax.set_xlim([min_trials, 50])
ax.set_ylim([30, 105])
plt.axhline(50, color='k', linestyle='--', label="Chance level")
plt.show(block=False)

for ev_num, ev in enumerate(rt_epochs.iter_evoked()):
    if ev_num >= 50:  # stop at 50
        break

    print("Just got epoch %d" % (ev_num + 1))

    if ev_num == 0:
        X = ev.data[np.newaxis, data_picks, :]
        y = int(ev.comment)  # the comment attribute contains the event_id
    else:
        X = np.concatenate((X, ev.data[np.newaxis, data_picks, :]), axis=0)
        y = np.append(y, int(ev.comment))

    if ev_num >= min_trials and ev_num % 5 == 0:
        cv = ShuffleSplit(5, test_size=0.2, random_state=42)  # 3 for speed
        scores_t = cross_val_score(concat_classifier, X, y, cv=cv,
                                   n_jobs=1) * 100

        std_scores.append(scores_t.std())
        scores.append(scores_t.mean())
        scores_x.append(ev_num)

        # Plot accuracy

        plt.plot(scores_x[-2:], scores[-2:], '-x', color='b',
                 label="Classif. score")
        ax.plot(scores_x[-1], scores[-1])

        hyp_limits = (np.asarray(scores) - np.asarray(std_scores),
                      np.asarray(scores) + np.asarray(std_scores))
        fill = plt.fill_between(scores_x, hyp_limits[0], y2=hyp_limits[1],
                                color='b', alpha=0.5)
        plt.pause(0.01)
        plt.draw()
        ax.collections.remove(fill)  # Remove old fill area

plt.fill_between(scores_x, hyp_limits[0], y2=hyp_limits[1], color='b',
                 alpha=0.5)
plt.draw()  # Final figure
```

It returns the following:
```
None
Opening raw data file C:\Users\mne_data\MNE-sample-data/MEG/sample/sample_audvis_filt-0-40_raw.fif...
    Read a total of 4 projection items:
        PCA-v1 (1 x 102)  idle
        PCA-v2 (1 x 102)  idle
        PCA-v3 (1 x 102)  idle
        Average EEG reference (1 x 60)  idle
    Range : 6450 ... 48149 =     42.956 ...   320.665 secs
Ready.
Reading 0 ... 41699  =      0.000 ...   277.709 secs...
Not setting metadata
No baseline correction applied
4 projection items activated
Traceback (most recent call last):
  File "C:/Users/Documents/test.py", line 40, in <module>
    rt_epochs = RtEpochs(rt_client, event_id, tmin, tmax, picks=picks, decim=1,
  File "<decorator-gen-469>", line 21, in __init__
  File "C:\Users\anaconda3\lib\site-packages\mne_realtime\epochs.py", line 155, in __init__
    super(RtEpochs, self).__init__(
  File "<decorator-gen-190>", line 21, in __init__
  File "C:\Users\anaconda3\lib\site-packages\mne\epochs.py", line 547, in __init__
    self._check_consistency()
  File "C:\Users\anaconda3\lib\site-packages\mne\epochs.py", line 557, in _check_consistency
    assert isinstance(self.drop_log, tuple)
AssertionError

```

Discussion:
codecov[bot]: # [Codecov](https://codecov.io/gh/mne-tools/mne-realtime/pull/22?src=pr&el=h1) Report
> Merging [#22](https://codecov.io/gh/mne-tools/mne-realtime/pull/22?src=pr&el=desc) (6fb2469) into [master](https://codecov.io/gh/mne-tools/mne-realtime/commit/cb06b789a2560e615dfd5f977590f3be1ab0e729?el=desc) (cb06b78) will **decrease** coverage by `13.96%`.
> The diff coverage is `78.26%`.


```diff
@@             Coverage Diff             @@
##           master      #22       +/-   ##
===========================================
- Coverage   70.42%   56.46%   -13.97%     
===========================================
  Files          15       15               
  Lines        1606     1633       +27     
  Branches      198      203        +5     
===========================================
- Hits         1131      922      -209     
- Misses        413      687      +274     
+ Partials       62       24       -38     
```



larsoner: CircleCI will be broken until we figure out a way to handle https://github.com/labstreaminglayer/liblsl-Python/issues/42. We can work around it using `subprocess` probably but it'll be a bit ugly.
jasmainak: Thanks a lot Eric. I can look into it after 12th. In the meanwhile perhaps @teonbrooks can review/try out the PR?

Patch:
diff --git a/.circleci/config.yml b/.circleci/config.yml
index f664627..0a604f6 100644
--- a/.circleci/config.yml
+++ b/.circleci/config.yml
@@ -2,24 +2,37 @@ version: 2
 jobs:
     build_docs:
       docker:
-        - image: circleci/python:3.7-stretch
+        - image: circleci/python:3.8.5-buster
       steps:
+        - restore_cache:
+            keys:
+              - source-cache-realtime
         - checkout
+        - run:
+            name: Complete checkout
+            command: |
+              if ! git remote -v | grep upstream; then
+                git remote add upstream git://github.com/mne-tools/mne-realtime.git
+              fi
+              git fetch upstream
+        - save_cache:
+            key: source-cache-realtime
+            paths:
+              - ".git"
         - run:
             name: Set BASH_ENV
             command: |
+              set -e
               echo "set -e" >> $BASH_ENV
-              echo "export SUBJECTS_DIR=~/mne_data/MNE-sample-data/subjects" >> $BASH_ENV
-              echo "export DISPLAY=:99" >> $BASH_ENV
               echo "export OPENBLAS_NUM_THREADS=4" >> $BASH_ENV
-              export MNE_ROOT=${PWD}/minimal_cmds
-              echo "export MNE_ROOT=${PWD}/minimal_cmds" >> $BASH_ENV
-              echo "export PATH=~/.local/bin/:${MNE_ROOT}/bin:$PATH" >> $BASH_ENV
-              curl https://staff.washington.edu/larsoner/minimal_cmds.tar.gz | tar xz
-              echo "export LD_LIBRARY_PATH=${MNE_ROOT}/lib:$LD_LIBRARY_PATH" >> $BASH_ENV
-              echo "export NEUROMAG2FT_ROOT=${PWD}/minimal_cmds/bin" >> $BASH_ENV
+              echo "export XDG_RUNTIME_DIR=/tmp/runtime-circleci" >> $BASH_ENV
+              wget -q -O tools/get_minimal_commands.sh https://raw.githubusercontent.com/mne-tools/mne-python/main/tools/get_minimal_commands.sh
+              source tools/get_minimal_commands.sh
+              echo "source ${PWD}/tools/get_minimal_commands.sh" >> $BASH_ENV
+              echo "export PATH=~/.local/bin/:$PATH" >> $BASH_ENV
               echo "BASH_ENV:"
               cat $BASH_ENV
+              mkdir -p ~/mne_data
         - run:
             name: check neuromag2ft
             command: |
@@ -32,35 +45,27 @@ jobs:
               echo ${CI_PULL_REQUEST//*pull\//} | tee merge.txt
               if [[ $(cat merge.txt) != "" ]]; then
                 echo "Merging $(cat merge.txt)";
-                git remote add upstream git://github.com/mne-tools/mne-realtime.git;
                 git pull --ff-only upstream "refs/pull/$(cat merge.txt)/merge";
-                git fetch upstream master;
               fi
 
-        # Load our data
-        - restore_cache:
-            keys:
-              - data-cache
-              - pip-cache
-
         - run:
-            name: Spin up Xvfb
+            name: Install 3D rendering libraries \ PyQt5 dependencies \ graphviz \ optipng (for optimized images)
             command: |
-              /sbin/start-stop-daemon --start --quiet --pidfile /tmp/custom_xvfb_99.pid --make-pidfile --background --exec /usr/bin/Xvfb -- :99 -screen 0 1400x900x24 -ac +extension GLX +render -noreset;
+              sudo apt-get install libosmesa6 libglx-mesa0 libopengl0 libglx0 libdbus-1-3 \
+                  libxkbcommon-x11-0 libxcb-icccm4 libxcb-image0 libxcb-keysyms1 libxcb-randr0 libxcb-render-util0 libxcb-shape0 libxcb-xfixes0 libxcb-xinerama0 \
+                  graphviz \
+                  optipng
 
-        # https://github.com/ContinuumIO/anaconda-issues/issues/9190#issuecomment-386508136
-        # https://github.com/golemfactory/golem/issues/1019
-        - run:
-            name: Fix libgcc_s.so.1 pthread_cancel bug
-            command: |
-              sudo apt-get install qt5-default
+        - restore_cache:
+            keys:
+              - pip-cache
 
         - run:
             name: Get Python running
             command: |
               pip install --user --upgrade --progress-bar off pip numpy
               pip install --user --upgrade --progress-bar off -r requirements.txt
-              pip install --user --upgrade --progress-bar off "https://api.github.com/repos/mne-tools/mne-python/zipball/master" "https://api.github.com/repos/rtfd/sphinx_rtd_theme/zipball/master" "https://api.github.com/repos/sphinx-gallery/sphinx-gallery/zipball/master" memory_profiler
+              pip install --user --upgrade --progress-bar off "sphinxcontrib-bibtex==2.0.0" "https://api.github.com/repos/mne-tools/mne-python/zipball/main" "https://api.github.com/repos/rtfd/sphinx_rtd_theme/zipball/master" "https://api.github.com/repos/sphinx-gallery/sphinx-gallery/zipball/master" memory_profiler
 
         - save_cache:
             key: pip-cache
@@ -77,7 +82,11 @@ jobs:
                python -c "import mne; mne.set_config('MNE_LOGGING_LEVEL', 'info')"
                python -c "import mne; level = mne.get_config('MNE_LOGGING_LEVEL'); assert level.lower() == 'info', repr(level)"
 
-        # Figure out if we should run a full, pattern, or noplot version
+        # Load our data
+        - restore_cache:
+            keys:
+              - data-cache
+
         - run:
             name: Get data
             command: |
diff --git a/.github/workflows/linux.yml b/.github/workflows/linux.yml
new file mode 100644
index 0000000..a849c5c
--- /dev/null
+++ b/.github/workflows/linux.yml
@@ -0,0 +1,45 @@
+name: 'linux / pip'
+on:
+  push:
+    branches:
+      - '*'
+  pull_request:
+    branches:
+      - '*'
+
+jobs:
+  job:
+    name: 'py3.9'
+    runs-on: ubuntu-20.04
+    defaults:
+      run:
+        shell: bash
+    steps:
+      - uses: actions/checkout@v2
+      - uses: actions/setup-python@v2
+        with:
+          python-version: '3.9'
+        name: 'Setup python'
+      - shell: bash -el {0}
+        run: |
+          wget -q -O tools/get_minimal_commands.sh https://raw.githubusercontent.com/mne-tools/mne-python/main/tools/get_minimal_commands.sh
+          source ./tools/get_minimal_commands.sh
+        name: 'Install neuromag2ft'
+      - shell: bash -el {0}
+        run: neuromag2ft --version
+        name: 'Test neuromag2ft'
+      - shell: bash -el {0}
+        run: ./tools/pip_dependencies.sh
+        name: 'Install MNE-Realtime'
+      - shell: bash -el {0}
+        run: mne sys_info
+        name: Print config
+      - shell: bash -el {0}
+        run: python -c "import mne; print(mne.datasets.testing.data_path(verbose=True))"
+        name: 'Get test data'
+      - shell: bash -el {0}
+        run: pytest mne_realtime
+        name: 'Run tests'
+      - uses: codecov/codecov-action@v1
+        if: success()
+        name: 'Upload coverage to CodeCov'
diff --git a/.github/workflows/macos.yml b/.github/workflows/macos.yml
new file mode 100644
index 0000000..cab7280
--- /dev/null
+++ b/.github/workflows/macos.yml
@@ -0,0 +1,40 @@
+name: 'macos / conda'
+on:
+  push:
+    branches:
+      - '*'
+  pull_request:
+    branches:
+      - '*'
+
+jobs:
+  job:
+    name: 'py3.6'
+    runs-on: macos-latest
+    defaults:
+      run:
+        shell: bash
+    steps:
+      - uses: actions/checkout@v2
+      - uses: conda-incubator/setup-miniconda@v2
+        with:
+          python-version: '3.7'
+          auto-update-conda: true
+          environment-file: 'environment.yml'
+          activate-environment: 'mne_realtime'
+        name: 'Setup conda'
+      - shell: bash -el {0}
+        run: ./tools/pip_dependencies.sh
+        name: 'Install MNE-Realtime'
+      - shell: bash -el {0}
+        run: mne sys_info
+        name: Print config
+      - shell: bash -el {0}
+        run: python -c "import mne; print(mne.datasets.testing.data_path(verbose=True))"
+        name: 'Get test data'
+      - shell: bash -el {0}
+        run: pytest mne_realtime
+        name: 'Run tests'
+      - uses: codecov/codecov-action@v1
+        if: success()
+        name: 'Upload coverage to CodeCov'
diff --git a/.travis.yml b/.travis.yml
deleted file mode 100644
index da18b3b..0000000
--- a/.travis.yml
+++ /dev/null
@@ -1,107 +0,0 @@
-language: c
-dist: xenial
-sudo: false
-cache:
-  apt: true
-  pip: false
-env:
-    # TRAVIS_PYTHON_VERSION is only needed for neo's setup.py
-    # OPENBLAS_NUM_THREADS=1 avoid slowdowns:
-    # https://github.com/xianyi/OpenBLAS/issues/731
-    global: PYTHON_VERSION=3.6 DISPLAY=:99.0 MNE_LOGGING_LEVEL=warning
-            PIP_DEPENDENCIES="codecov nitime"
-            TRAVIS_PYTHON_VERSION=3.6 CONDA_VERSION=">=4.3.27"
-            OPENBLAS_NUM_THREADS=1
-
-matrix:
-    include:
-        # No data + style testing
-        - os: linux
-          env: DEPS=nodata MNE_DONTWRITE_HOME=true MNE_FORCE_SERIAL=true MNE_SKIP_NETWORK_TEST=1
-               CONDA_DEPENDENCIES="numpy scipy matplotlib sphinx"
-               PIP_DEPENDENCIES="flake8 numpydoc codespell pydocstyle codecov check-manifest  pytest pytest-timeout pytest-cov pytest-mock pytest-sugar pytest-faulthandler"
-
-        # Linux
-        - os: linux
-          env: CONDA_ENVIRONMENT="environment.yml"
-
-        # OSX conda
-        - os: osx
-          env: CONDA_ENVIRONMENT="environment.yml"
-
-        # PIP + non-default stim channel
-        - os: linux
-          env: MNE_STIM_CHANNEL=STI101
-               PYTHON_VERSION=3.7
-          language: python
-          python: "3.7"
-          addons:
-            apt:
-              packages:
-                - qt5-default  # Qt 5.12 release needs this to avoid a missing library bug
-
-# Setup anaconda
-before_install:
-    - if [ "${TRAVIS_OS_NAME}" == "linux" ]; then
-        /sbin/start-stop-daemon --start --quiet --pidfile /tmp/custom_xvfb_99.pid --make-pidfile --background --exec /usr/bin/Xvfb -- :99 -screen 0 1400x900x24 -ac +extension GLX +render -noreset;
-      fi;
-    - if [ -z "$CONDA_ENVIRONMENT" ] && [ -z "$CONDA_DEPENDENCIES" ]; then
-        pip uninstall -y numpy;
-        pip install -f "https://7933911d6844c6c53a7d-47bd50c35cd79bd838daf386af554a83.ssl.cf2.rackcdn.com" --pre numpy scipy;
-        pip install --upgrade $PIP_DEPENDENCIES;
-        pip install --upgrade -r requirements.txt;
-      else
-        git clone https://github.com/astropy/ci-helpers.git;
-        source ci-helpers/travis/setup_conda.sh;
-        if [ ! -z "$CONDA_ENVIRONMENT" ]; then
-          pip uninstall --yes mne;
-        fi;
-        if [ "${TRAVIS_OS_NAME}" == "osx" ]; then
-          pip install --upgrade pyqt5>=5.10 pylsl;
-        else
-          conda install -c tstenner pylsl;
-        fi;
-      fi
-    # Don't source mne_setup_sh here because changing PATH etc. can't be done in a script
-    - if [ "${DEPS}" == "" ]; then
-        export MNE_ROOT="${PWD}/minimal_cmds";
-        export PATH=${MNE_ROOT}/bin:$PATH;
-        if [ "${TRAVIS_OS_NAME}" == "linux" ]; then
-          curl https://staff.washington.edu/larsoner/minimal_cmds.tar.gz | tar xz;
-          export LD_LIBRARY_PATH=${MNE_ROOT}/lib:$LD_LIBRARY_PATH;
-          export NEUROMAG2FT_ROOT="${PWD}/minimal_cmds/bin";
-        else
-          curl https://staff.washington.edu/larsoner/minimal_cmds_osx.tar.gz | tar xz;
-          export DYLD_LIBRARY_PATH=${MNE_ROOT}/lib:$DYLD_LIBRARY_PATH;
-        fi;
-        mne_surf2bem --version;
-      fi;
-
-install:
-    # Rvm overrides cd with a function so that it can hook into it to run
-    # some scripts, see https://github.com/travis-ci/travis-ci/issues/8703
-    - if [ "${TRAVIS_OS_NAME}" == "osx" ]; then
-        unset -f cd;
-      fi;
-    - python setup.py develop
-    - python -c "import mne; mne.sys_info()"
-    # Trigger download of testing data
-    - if [ "${DEPS}" != "nodata" ]; then
-        python -c 'import mne; mne.datasets.testing.data_path(verbose=True)';
-        ls -al $HOME/mne_data/;
-        ls -al $HOME/mne_data/MNE-testing-data/;
-        ls -al $HOME/mne_data/MNE-testing-data/MEG/;
-        ls -al $HOME/mne_data/MNE-testing-data/MEG/sample/;
-      else
-        export MNE_SKIP_TESTING_DATASET_TESTS=true;
-      fi;
-
-script:
-    - python -c "import mne; print(mne.sys_info())"
-    - pytest mne_realtime
-    - if [ "${DEPS}" == "nodata" ]; then
-        make pep;
-      fi;
-
-after_script:
-    - codecov
diff --git a/README.rst b/README.rst
index d20203e..d151ccb 100644
--- a/README.rst
+++ b/README.rst
@@ -1,9 +1,6 @@
 .. -*- mode: rst -*-
 
-|Travis|_ |Azure|_ |CircleCI|_ |Codecov|_
-
-.. |Travis| image:: https://travis-ci.com/mne-tools/mne-realtime.svg?branch=master
-.. _Travis: https://travis-ci.com/mne-tools/mne-realtime/branches
+|Azure|_ |CircleCI|_ |Codecov|_
 
 .. |Azure| image:: https://dev.azure.com/mne-tools/mne-realtime/_apis/build/status/mne-tools.mne-realtime?branchName=master
 .. _Azure: https://dev.azure.com/mne-tools/mne-realtime/_build/latest?definitionId=1&branchName=master
@@ -19,14 +16,14 @@ MNE-realtime
 
 This is a repository for realtime analysis of MEG/EEG data with MNE. The documentation can be found here:
 
-   * `Examples <https://mne.tools/mne-realtime/auto_examples/index.html>`_
+   * `Examples`_
    * `API`_
 
 Dependencies
 ------------
 
-* numpy
-* MNE
+* `numpy`_
+* `MNE`_
 
 Installation
 ------------
@@ -79,4 +76,7 @@ Bug reports
 Use the `github issue tracker <https://github.com/mne-tools/mne-realtime/issues>`_
 to report bugs.
 
- .. _API: <https://mne.tools/mne-realtime/api.html>
+ .. _API: https://mne.tools/mne-realtime/api.html
+ .. _Examples: https://mne.tools/mne-realtime/auto_examples/index.html
+ .. _numpy: https://numpy.org
+ .. _MNE: https://mne.tools
\ No newline at end of file
diff --git a/azure-pipelines.yml b/azure-pipelines.yml
index 4eadfce..f6421a4 100644
--- a/azure-pipelines.yml
+++ b/azure-pipelines.yml
@@ -13,16 +13,14 @@ jobs:
   variables:
     MNE_LOGGING_LEVEL: 'warning'
     MNE_FORCE_SERIAL: 'true'
-    PIP_DEPENDENCIES: 'codecov'
     OPENBLAS_NUM_THREADS: 1
     AZURE_CI_WINDOWS: 'true'
   strategy:
     maxParallel: 4
     matrix:
-      Python37-64bit-full-pip:
-        PYTHON_VERSION: '3.7'
+      Python39-64bit-full-pip:
+        PYTHON_VERSION: '3.9'
         PYTHON_ARCH: 'x64'
-        TEST_MODE: 'pip'
   steps:
   - task: UsePythonVersion@0
     inputs:
@@ -34,28 +32,11 @@ jobs:
       Set-StrictMode -Version Latest
       $ErrorActionPreference = "Stop"
       $PSDefaultParameterValues['*:ErrorAction']='Stop'
-      pip install --upgrade numpy scipy vtk
+      pip install --upgrade --pre numpy scipy matplotlib
+      pip install https://api.github.com/repos/mne-tools/mne-python/zipball/main
       pip install --upgrade -r requirements.txt
-      pip install $env:PIP_DEPENDENCIES
-      pip install pylsl
-    condition: eq(variables['TEST_MODE'], 'pip')
+      pip install codecov pylsl
     displayName: 'Install dependencies with pip'
-  - powershell: |
-      Set-StrictMode -Version Latest
-      $ErrorActionPreference = "Stop"
-      $PSDefaultParameterValues['*:ErrorAction']='Stop'
-      $env:PYTHON = '$(System.DefaultWorkingDirectory)' + '\conda'
-      git clone git://github.com/astropy/ci-helpers.git
-      cd ci-helpers
-      git checkout nopip
-      cd ..
-      powershell ci-helpers/appveyor/install-miniconda.ps1
-      $env:PATH = $env:PYTHON + "\envs\test;" + $env:PYTHON + "\envs\test\Scripts;" + $env:PYTHON + ";" + $env:PYTHON + "\Scripts;" + $env:PATH
-      pip uninstall -yq mne
-      pip install pylsl
-      Write-Host ("##vso[task.setvariable variable=PATH]" + $env:PATH)
-    condition: eq(variables['TEST_MODE'], 'conda')
-    displayName: 'Install dependencies with conda'
   - script: python setup.py develop
     displayName: 'Install MNE-Realtime'
   - script: python -c "import mne; print(mne.sys_info())"
diff --git a/codecov.yml b/codecov.yml
index 1e7b888..fda3126 100644
--- a/codecov.yml
+++ b/codecov.yml
@@ -1,14 +1,23 @@
+comment: false
+
+codecov:
+  notify:
+    require_ci_to_pass: no
+
 coverage:
-  precision: 2
-  round: down
-  range: "70...100"
   status:
-    project:
+    patch:
       default:
-        target: auto
-        threshold: 0.01
-    patch: false
-    changes: false
-comment:
-  layout: "header, diff, sunburst, uncovered"
-  behavior: default
+        informational: true
+        target: 95%
+        if_no_uploads: error
+        if_not_found: success
+        if_ci_failed: failure
+    project:
+      default: false
+      library:
+        informational: true
+        target: 90%
+        if_no_uploads: error
+        if_not_found: success
+        if_ci_failed: failure
diff --git a/doc/conf.py b/doc/conf.py
index b2c2283..3d8afc2 100644
--- a/doc/conf.py
+++ b/doc/conf.py
@@ -44,6 +44,7 @@ extensions = [
     'sphinx.ext.viewcode',
     'numpydoc',
     'sphinx_gallery.gen_gallery',
+    'sphinxcontrib.bibtex',
 ]
 
 # this is needed for some reason...
@@ -295,15 +296,22 @@ texinfo_documents = []
 # intersphinx configuration
 intersphinx_mapping = {
     'python': ('https://docs.python.org/3', None),
-    'numpy': ('https://www.numpy.org/devdocs', None),
+    'numpy': ('https://numpy.org/devdocs', None),
     'scipy': ('https://scipy.github.io/devdocs', None),
     'matplotlib': ('https://matplotlib.org', None),
     'sklearn': ('https://scikit-learn.org/stable', None),
     'joblib': ('https://joblib.readthedocs.io/en/latest', None),
     'pandas': ('https://pandas.pydata.org/pandas-docs/stable', None),
-    'mne': ('https://mne-tools.github.io/dev', None),
+    'mne': ('https://mne.tools/dev', None),
 }
 
+##############################################################################
+# sphinxcontrib-bibtex
+
+bibtex_bibfiles = ['./references.bib']
+bibtex_style = 'unsrt'
+bibtex_footbibliography_header = ''
+
 ##############################################################################
 # sphinx-gallery
 
@@ -369,7 +377,7 @@ sphinx_gallery_conf = {
     'default_thumb_file': os.path.join('_static', 'mne_helmet.png'),
     'backreferences_dir': 'generated',
     'plot_gallery': 'True',  # Avoid annoying Unicode/bool default warning
-    'download_section_examples': False,
+    'download_all_examples': False,
     'thumbnail_size': (160, 112),
     'min_reported_time': 1.,
     'abort_on_example_error': False,
@@ -400,7 +408,7 @@ numpydoc_xref_aliases = {
     'Epochs': 'mne.Epochs', 'Layout': 'mne.channels.Layout',
     'Raw': 'mne.io.Raw', 'Covariance': 'mne.Covariance',
     'Evoked': 'mne.Evoked', 'Info': 'mne.Info',
-    'Montage': 'mne.channels.Montage',
+    'Montage': 'mne.channels.Montage', 'Forward': 'mne.Forward',
     'DigMontage': 'mne.channels.DigMontage',
     # mne_realtime
     'RtEpochs': 'mne_realtime.RtEpochs',
diff --git a/doc/references.bib b/doc/references.bib
new file mode 100644
index 0000000..dd6ffc6
--- /dev/null
+++ b/doc/references.bib
@@ -0,0 +1,26 @@
+% Encoding: UTF-8
+
+@article{SavitzkyGolay1964,
+  author = {Savitzky, Abraham and Golay, Marcel J. E.},
+  doi = {10.1021/ac60214a047},
+  journal = {Analytical Chemistry},
+  number = {8},
+  pages = {1627-1639},
+  title = {Smoothing and Differentiation of Data by Simplified Least Squares Procedures},
+  volume = {36},
+  year = {1964}
+}
+
+@article{Yao2001,
+	title = {A method to standardize a reference of scalp {EEG} recordings to a point at infinity},
+	volume = {22},
+	issn = {0967-3334},
+	doi = {10.1088/0967-3334/22/4/305},
+	number = {4},
+	journal = {Physiological Measurement},
+	author = {Yao, D.},
+	month = nov,
+	year = {2001},
+	pmid = {11761077},
+	pages = {693--711}
+}
diff --git a/environment.yml b/environment.yml
index 4b62a2b..391f901 100644
--- a/environment.yml
+++ b/environment.yml
@@ -2,27 +2,20 @@ name: mne_realtime
 channels:
 - defaults
 dependencies:
-- python<3.7
+- python
 - pip
-- mkl
 - numpy
 - scipy
 - matplotlib
-- pyqt>=5.9
+- pyqt
 - scikit-learn
 - pillow
 - pytest
 - pytest-cov
-- pytest-mock
-- pytest-timeout
-- joblib
-- psutil
 - numpydoc
 - flake8
 - pip:
-  - mne
-  - pytest-faulthandler
-  - pytest-sugar
+  - https://api.github.com/repos/mne-tools/mne-python/zipball/main
   - pydocstyle
   - codespell
   - pylsl>=1.12
diff --git a/mne_realtime/__init__.py b/mne_realtime/__init__.py
index 239ac05..372b7ff 100644
--- a/mne_realtime/__init__.py
+++ b/mne_realtime/__init__.py
@@ -18,5 +18,5 @@ from .stim_server_client import StimServer, StimClient
 from .fieldtrip_client import FieldTripClient
 
 
-__all__ = ['__version__', 'FieldTripClient', 'LSLClient', 'MockRtClient',
-           'RtClient', 'RtEpochs', 'StimClient', 'StimServer']
+__all__ = ['__version__', 'FieldTripClient', 'LSLClient', 'MockLSLStream',
+           'MockRtClient', 'RtClient', 'RtEpochs', 'StimClient', 'StimServer']
diff --git a/mne_realtime/base_client.py b/mne_realtime/base_client.py
index 9726c11..db78023 100644
--- a/mne_realtime/base_client.py
+++ b/mne_realtime/base_client.py
@@ -61,7 +61,6 @@ class _BaseClient(object):
         self._recv_callbacks = list()
 
     def __enter__(self):  # noqa: D105
-
         # connect to buffer
         logger.info("Client: Waiting for server to start")
         start_time = time.time()
@@ -70,10 +69,11 @@ class _BaseClient(object):
                 self._connect()
                 logger.info("Client: Connected")
                 break
-            except Exception:
+            except Exception as exc:
+                print(exc)
                 time.sleep(0.1)
         else:
-            raise RuntimeError('Could not connect to Client.')
+            self._connection_error()
 
         if not self.info:
             self.info = self._create_info()
@@ -81,6 +81,9 @@ class _BaseClient(object):
 
         return self
 
+    def _connection_error(self, extra=''):
+        raise RuntimeError(f'Could not connect to Client.{extra}')
+
     def __exit__(self, type, value, traceback):
         self._disconnect()
 
diff --git a/mne_realtime/epochs.py b/mne_realtime/epochs.py
index 6eee059..1730df5 100644
--- a/mne_realtime/epochs.py
+++ b/mne_realtime/epochs.py
@@ -11,6 +11,7 @@ import copy
 import numpy as np
 
 from mne import pick_channels
+from mne.io.pick import _picks_to_idx
 from mne.utils import logger, verbose, fill_doc
 from mne.epochs import BaseEpochs
 from mne.event import _find_events
@@ -151,6 +152,10 @@ class RtEpochs(BaseEpochs):
         self._events = list()
         self._selection = list()
 
+        # Number of good and bad epochs received
+        self._n_good = 0
+        self._n_bad = 0
+
         # call BaseEpochs constructor
         super(RtEpochs, self).__init__(
             info, None, None, event_id, tmin, tmax, baseline, picks=picks,
@@ -201,10 +206,6 @@ class RtEpochs(BaseEpochs):
         self._first_samp = 0
         self._event_backlog = list()
 
-        # Number of good and bad epochs received
-        self._n_good = 0
-        self._n_bad = 0
-
         self._started = False
         self._last_time = time.time()
 
@@ -350,7 +351,7 @@ class RtEpochs(BaseEpochs):
     next = __next__
 
     @verbose
-    def _get_data(self, out=True, picks=None,  verbose=None):
+    def _get_data(self, out=True, picks=None, item=None, verbose=None):
         """
         Return all data as numpy array.
 
@@ -374,16 +375,14 @@ class RtEpochs(BaseEpochs):
         not on data loading.
         """
         if out:
-            epochs = list()
-            for epoch in self._epoch_queue:
-                epochs.append(epoch)
-
-            data = np.array(epochs)
-
-            if picks is not None:
-                picks = _picks_to_idx(self.info, picks)
-                data = data[:, picks]
-            return data
+            item = slice(None) if item is None else item
+            select = self._item_to_select(item)  # indices or slice
+            use_idx = np.arange(len(self._events))[select]
+            if picks is None:
+                picks = slice(None)
+            else:
+                picks = _picks_to_idx(self.info, picks, none='all', exclude=())
+            return np.array([self._epoch_queue[idx][picks] for idx in use_idx])
 
     def _process_raw_buffer(self, raw_buffer):
         """Process raw buffer (callback from RtClient).
@@ -409,11 +408,11 @@ class RtEpochs(BaseEpochs):
         raw_buffer = self._cals * raw_buffer
 
         # detect events
-        data = np.abs(raw_buffer[self._stim_picks]).astype(np.int)
+        data = np.abs(raw_buffer[self._stim_picks]).astype(np.int64)
         # if there is a previous buffer check the last samples from it too
         if self._last_buffer is not None:
-            prev_data = self._last_buffer[self._stim_picks,
-                                          -raw_buffer.shape[1]:].astype(np.int)
+            prev_data = self._last_buffer[
+                self._stim_picks, -raw_buffer.shape[1]:].astype(np.int64)
             data = np.concatenate((prev_data, data), axis=1)
             data = np.atleast_2d(data)
             buff_events = _find_events(data,
@@ -507,7 +506,12 @@ class RtEpochs(BaseEpochs):
         epoch = epoch[self.picks, :]
 
         # Detrend, baseline correct, decimate
-        epoch = self._detrend_offset_decim(epoch, verbose='ERROR')
+        kwargs = dict()
+        try:  # Needed on MNE 0.23+
+            kwargs['picks'] = self._detrend_picks
+        except AttributeError:
+            pass
+        epoch = self._detrend_offset_decim(epoch, verbose='ERROR', **kwargs)
 
         # apply SSP
         epoch = self._project_epoch(epoch)
@@ -519,11 +523,11 @@ class RtEpochs(BaseEpochs):
         if is_good:
             self._epoch_queue.append(epoch)
             self._events.append((event_samp, 0, event_id))
-            self.drop_log.append(list())
+            self.drop_log = self.drop_log + (tuple(),)
             self._selection.append(len(self.drop_log) - 1)
             self._n_good += 1
         else:
-            self.drop_log.append(offending_reasons)
+            self.drop_log = self.drop_log + (tuple(offending_reasons),)
             self._n_bad += 1
 
     @verbose
diff --git a/mne_realtime/lsl_client.py b/mne_realtime/lsl_client.py
index 66c5970..eefac33 100644
--- a/mne_realtime/lsl_client.py
+++ b/mne_realtime/lsl_client.py
@@ -86,13 +86,29 @@ class LSLClient(_BaseClient):
         # To use this function with an LSL stream which has a 'name' but no
         # 'source_id', change the keyword in pylsl.resolve_byprop accordingly.
         pylsl = _check_pylsl_installed(strict=True)
-        stream_info = pylsl.resolve_byprop('source_id', self.host,
-                                           timeout=self.wait_max)[0]
+        print(f'Looking for LSL stream {self.host}...')
+        # resolve_byprop is a bit fragile
+        streams = pylsl.resolve_streams(wait_time=min(0.1, self.wait_max))
+        ids = list()
+        for stream_info in streams:
+            ids.append(stream_info.source_id())
+            if ids[-1] == self.host:
+                break
+        else:
+            raise RuntimeError(f'{self.host} not found in streams: {ids}')
+        print(f'Found stream {repr(stream_info.name())} via '
+              f'{stream_info.source_id()}...')
         self.client = pylsl.StreamInlet(info=stream_info,
                                         max_buflen=self.buffer_size)
 
         return self
 
+    def _connection_error(self):
+        pylsl = _check_pylsl_installed(strict=True)
+        extra = (f' Available streams on {self.host} from resolve_streams():\n'
+                 f'{pylsl.resolve_streams()}')
+        super()._connection_error(extra)
+
     def _create_info(self):
         montage = None
         sfreq = self.client.info().nominal_srate()
diff --git a/mne_realtime/mock_lsl_stream.py b/mne_realtime/mock_lsl_stream.py
index b38a289..cc6dec5 100644
--- a/mne_realtime/mock_lsl_stream.py
+++ b/mne_realtime/mock_lsl_stream.py
@@ -22,9 +22,11 @@ class MockLSLStream(object):
     time_dilation : int
         A scale factor to speed up or slow down the rate of
         the data being streamed.
+    status : bool
+        If True, give status updates every ``sfreq`` samples.
     """
 
-    def __init__(self, host, raw, ch_type, time_dilation=1):
+    def __init__(self, host, raw, ch_type, time_dilation=1, status=False):
         self._host = host
         self._ch_type = ch_type
         self._time_dilation = time_dilation
@@ -32,20 +34,21 @@ class MockLSLStream(object):
         raw.load_data().pick(ch_type)
         self._raw = raw
         self._sfreq = int(self._raw.info['sfreq'])
+        self._status = bool(status)
 
     def start(self):
         """Start a mock LSL stream."""
-        print("now sending data...")
-        self.process = Process(target=self._initiate_stream)
-        self.process.daemon = True
+        if self._status:
+            print("Now sending data...")
+        self.process = Process(target=self._initiate_stream, daemon=True)
         self.process.start()
-
         return self
 
     def stop(self):
         """Stop a mock LSL stream."""
         self._streaming = False
-        print("Stopping stream...")
+        if self._status:
+            print("Stopping stream...")
         self.process.terminate()
         return self
 
@@ -82,9 +85,13 @@ class MockLSLStream(object):
 
         # let's make some data
         counter = 0
+        every = max(int(round(self._sfreq)), 1)
         while self._streaming:
             mysample = self._raw[:, counter][0].ravel().tolist()
             # now send it and wait for a bit
+            if self._status and counter % every == 0:
+                print(f'Sending sample {counter} on {self._host}, '
+                      f'have_consumers={outlet.have_consumers()}')
             outlet.push_sample(mysample)
             counter = 0 if counter == self._raw.last_samp else counter + 1
             time.sleep(self._time_dilation / self._sfreq)
diff --git a/requirements.txt b/requirements.txt
index 6212dd8..a966711 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -7,14 +7,8 @@ sip
 scikit-learn
 pytest
 pytest-cov
-pytest-faulthandler
-pytest-mock
-pytest-sugar
-pytest-timeout
-joblib
-psutil
 numpydoc
 pydocstyle
 flake8
 pylsl>=1.12
-mne
+https://api.github.com/repos/mne-tools/mne-python/zipball/main
diff --git a/setup.cfg b/setup.cfg
index 4c2f4bd..9b1f401 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -20,9 +20,10 @@ ignore = E241,E305,W504
 
 [tool:pytest]
 addopts =
-    --showlocals --durations=20 --doctest-modules -ra --cov-report=
+    --durations=20 --doctest-modules -ra --cov-report= --tb=short
     --doctest-ignore-import-errors --junit-xml=junit-results.xml
     --ignore=doc --ignore=examples --cov=mne_realtime
+    --capture=sys
 
 [pycodestyle]
 exclude = __init__.py,*externals*,constants.py,fixes.py
diff --git a/setup.py b/setup.py
index b50df9b..9b02f49 100644
--- a/setup.py
+++ b/setup.py
@@ -32,10 +32,11 @@ CLASSIFIERS = ['Intended Audience :: Science/Research',
                'Operating System :: POSIX',
                'Operating System :: Unix',
                'Operating System :: MacOS',
-               'Programming Language :: Python :: 2.7',
-               'Programming Language :: Python :: 3.5',
                'Programming Language :: Python :: 3.6',
-               'Programming Language :: Python :: 3.7']
+               'Programming Language :: Python :: 3.7',
+               'Programming Language :: Python :: 3.8',
+               'Programming Language :: Python :: 3.9',
+               ]
 EXTRAS_REQUIRE = {
     'tests': [
         'pytest',
diff --git a/tools/pip_dependencies.sh b/tools/pip_dependencies.sh
new file mode 100755
index 0000000..dd4775d
--- /dev/null
+++ b/tools/pip_dependencies.sh
@@ -0,0 +1,6 @@
+#!/bin/bash -ef
+
+python -m pip install --upgrade pip
+python -m pip install --upgrade setuptools wheel
+python -m pip install -r requirements.txt
+python -m pip install -ve .


Test patch:
diff --git a/mne_realtime/conftest.py b/mne_realtime/conftest.py
index 3c4abe2..7285b73 100644
--- a/mne_realtime/conftest.py
+++ b/mne_realtime/conftest.py
@@ -20,6 +20,7 @@ def pytest_configure(config):
     # - This list should also be considered alongside reset_warnings in
     #   doc/conf.py.
     warning_lines = """
+    ignore:.*may indicate binary incompatibility.*:RuntimeWarning
     error::
     """  # noqa: E501
     for warning_line in warning_lines.split('\n'):
diff --git a/mne_realtime/tests/test_fieldtrip_client.py b/mne_realtime/tests/test_fieldtrip_client.py
index 3af7df7..7d9c084 100644
--- a/mne_realtime/tests/test_fieldtrip_client.py
+++ b/mne_realtime/tests/test_fieldtrip_client.py
@@ -18,7 +18,6 @@ import pytest
 from mne import Epochs, find_events, pick_types
 from mne.io import read_raw_fif
 from mne.utils import requires_neuromag2ft
-from mne.utils import run_tests_if_main
 from mne_realtime import FieldTripClient, RtEpochs
 
 from mne_realtime.tests.test_mock_client import _call_base_epochs_public_api
@@ -165,6 +164,3 @@ def test_fieldtrip_client(free_tcp_port):
         assert n_channels2 == len(picks)
     finally:
         kill_signal.put(False)  # stop the buffer
-
-
-run_tests_if_main()
diff --git a/mne_realtime/tests/test_lsl_client.py b/mne_realtime/tests/test_lsl_client.py
index 027a6a5..50cac18 100644
--- a/mne_realtime/tests/test_lsl_client.py
+++ b/mne_realtime/tests/test_lsl_client.py
@@ -1,10 +1,9 @@
 # Author: Teon Brooks <teon.brooks@gmail.com>
 #
 # License: BSD (3-clause)
-from os import getenv, path as op
-import pytest
+from os import path as op
 
-from mne.utils import run_tests_if_main, requires_pylsl
+from mne.utils import requires_pylsl
 from mne.io import read_raw_fif
 from mne.datasets import testing
 
@@ -18,33 +17,20 @@ raw_fname = op.join(base_dir, 'test_raw.fif')
 
 @requires_pylsl
 @testing.requires_testing_data
-@pytest.mark.skipif(getenv('AZURE_CI_WINDOWS', 'false').lower() == 'true',
-                    reason=('Running multiprocessing on Windows ' +
-                            'creates a BrokenPipeError, see ' +
-                            'https://stackoverflow.com/questions/50079165/'))
 def test_lsl_client():
     """Test the LSLClient for connection and data retrieval."""
-    wait_max = 10
-
     raw = read_raw_fif(raw_fname)
     n_secs = 1
     raw.crop(n_secs)
     raw_info = raw.info
     sfreq = raw_info['sfreq']
-    stream = MockLSLStream(host, raw, ch_type='eeg')
-    stream.start()
-
-    with LSLClient(info=raw_info, host=host, wait_max=wait_max) as client:
-        client_info = client.get_measurement_info()
-        epoch = client.get_data_as_epoch(n_samples=sfreq * n_secs * 2)
+    with MockLSLStream(host, raw, ch_type='eeg', status=True):
+        with LSLClient(info=raw_info, host=host, wait_max=5) as client:
+            client_info = client.get_measurement_info()
+            epoch = client.get_data_as_epoch(n_samples=sfreq * n_secs * 2)
 
     assert client_info['nchan'] == raw_info['nchan']
     assert ([ch["ch_name"] for ch in client_info["chs"]] ==
             [ch_name for ch_name in raw_info['ch_names']])
 
     assert raw_info['nchan'], sfreq == epoch.get_data().shape[1:]
-
-    stream.stop()
-
-
-run_tests_if_main()
diff --git a/mne_realtime/tests/test_mock_client.py b/mne_realtime/tests/test_mock_client.py
index 5901b02..33677d4 100644
--- a/mne_realtime/tests/test_mock_client.py
+++ b/mne_realtime/tests/test_mock_client.py
@@ -9,7 +9,6 @@ import pytest
 from mne import (Epochs, read_events, read_epochs, find_events, create_info,
                  pick_channels, pick_types, concatenate_raws)
 from mne.io import RawArray, read_raw_fif
-from mne.utils import run_tests_if_main
 from mne.datasets import testing
 
 from mne_realtime import MockRtClient, RtEpochs
@@ -142,6 +141,13 @@ def test_get_event_data():
     assert_array_equal(rt_data, data)
 
 
+def _update_times(raw):
+    try:  # on MNE 0.23+ this is not needed
+        raw._update_times()
+    except AttributeError:
+        pass
+
+
 def test_find_events():
     """Test find_events in rt_epochs."""
     raw = read_raw_fif(raw_fname, preload=True, verbose=False)
@@ -164,7 +170,7 @@ def test_find_events():
     raw._data[stim_channel_idx, 520:530] = 6
     raw._data[stim_channel_idx, 530:532] = 5
     raw._data[stim_channel_idx, 540] = 6
-    raw._update_times()
+    _update_times(raw)
 
     # consecutive=False
     find_events = dict(consecutive=False)
@@ -228,7 +234,7 @@ def test_find_events():
     # Test that we can handle events at the beginning of the buffer
     raw._data[stim_channel_idx, :] = 0
     raw._data[stim_channel_idx, 1000:1005] = 5
-    raw._update_times()
+    _update_times(raw)
 
     # Check that we find events that start at the beginning of the buffer
     find_events = dict(consecutive=False)
@@ -249,7 +255,7 @@ def test_find_events():
     # Test that we can handle events over different buffers
     raw._data[stim_channel_idx, :] = 0
     raw._data[stim_channel_idx, 997:1003] = 5
-    raw._update_times()
+    _update_times(raw)
     for min_dur in [0.002, 0.004]:
         find_events = dict(consecutive=False, min_duration=min_dur)
         rt_client = MockRtClient(raw)
@@ -284,10 +290,11 @@ def test_rejection(buffer_size):
     epochs_to_reject = [1, 3]
     epochs_to_keep = np.setdiff1d(np.arange(len(epoch_start_samples)),
                                   epochs_to_reject)
-    expected_drop_log = [list() for _ in range(len(epoch_start_samples))]
+    expected_drop_log = [tuple() for _ in range(len(epoch_start_samples))]
     for cur_epoch in epochs_to_reject:
         raw_array[1, epoch_start_samples[cur_epoch]] = reject_threshold + 1
-        expected_drop_log[cur_epoch] = [ch_names[1]]
+        expected_drop_log[cur_epoch] = (ch_names[1],)
+    expected_drop_log = tuple(expected_drop_log)
 
     raw = RawArray(raw_array, info)
     events = find_events(raw, shortest_event=1, initial_event=True)
@@ -300,7 +307,7 @@ def test_rejection(buffer_size):
     assert len(epochs) == len(epoch_start_samples) - len(epochs_to_reject)
     assert_array_equal(epochs_data[:, 1, 0],
                        raw_array[1, epoch_start_samples[epochs_to_keep]])
-    assert_array_equal(epochs.drop_log, expected_drop_log)
+    assert epochs.drop_log == expected_drop_log
     assert_array_equal(epochs.selection, epochs_to_keep)
 
     rt_client = MockRtClient(raw)
@@ -315,7 +322,7 @@ def test_rejection(buffer_size):
                         buffer_size=buffer_size)
 
     assert len(rt_epochs) == len(epochs_to_keep)
-    assert_array_equal(rt_epochs.drop_log, expected_drop_log)
+    assert rt_epochs.drop_log == expected_drop_log
     assert_array_equal(rt_epochs.selection, epochs_to_keep)
     rt_data = rt_epochs.get_data()
     assert rt_data.shape == epochs_data.shape
@@ -382,6 +389,3 @@ def test_events_long():
     y_offline = epochs_offline.events[:, 2]
     assert_array_equal(X_rt, X_offline)
     assert_array_equal(y_rt, y_offline)
-
-
-run_tests_if_main()
diff --git a/mne_realtime/tests/test_stim_client_server.py b/mne_realtime/tests/test_stim_client_server.py
index da1f028..138367f 100644
--- a/mne_realtime/tests/test_stim_client_server.py
+++ b/mne_realtime/tests/test_stim_client_server.py
@@ -3,7 +3,7 @@ import threading
 import time
 
 import pytest
-from mne.utils import requires_good_network, run_tests_if_main
+from mne.utils import requires_good_network
 
 from mne_realtime import StimServer, StimClient
 
@@ -77,6 +77,3 @@ def _connect_client(trig_queue):
 
     trig_queue.put(stim_client.get_trigger())
     stim_client.close()
-
-
-run_tests_if_main()