Skip to content

Commit 69935ba

Browse files
committed
docs: finalized JOSS paper.md with ORCID and updated references
1 parent d71d273 commit 69935ba

6 files changed

Lines changed: 233 additions & 0 deletions

File tree

CONTRIBUTING.md

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
# Contributing to `python-oephys`
2+
3+
Thank you for your interest in contributing to `python-oephys`! We welcome contributions from the community, whether they are bug reports, feature requests, documentation improvements, or new code.
4+
5+
## 🐛 Reporting Bugs
6+
7+
If you find a bug, please open an issue on our [GitHub Issues](https://github.com/Neuro-Mechatronics-Interfaces/python-open-ephys/issues) page. Please include:
8+
- A clear and descriptive title.
9+
- Steps to reproduce the bug.
10+
- Actual vs. expected behavior.
11+
- Your OS and Python version.
12+
13+
## ✨ Feature Requests
14+
15+
We’re always looking for ways to improve! If you have an idea for a new feature, please open an issue and describe:
16+
- What problem it solves.
17+
- How it should work (ideally with a code snippet or pseudocode).
18+
19+
## 🔧 Pull Request Guidelines
20+
21+
1. **Fork the repository** and create your branch from `main`.
22+
2. **Follow the style**: Use PEP 8 for Python code and provide clear docstrings.
23+
3. **Add tests**: If you're adding code, please add corresponding tests in the `tests/` directory.
24+
4. **Update documentation**: If your change affects the API or CLI, update the docstrings and README.
25+
5. **Run tests**: Ensure all tests pass before submitting your PR:
26+
```bash
27+
pytest
28+
```
29+
30+
## 🧪 Development Setup
31+
32+
1. Clone the repo: `git clone https://github.com/Neuro-Mechatronics-Interfaces/python-open-ephys.git`
33+
2. Install with development dependencies:
34+
```bash
35+
pip install -e ".[gui,ml,docs]"
36+
pip install pytest pytest-cov
37+
```
38+
39+
## 📜 Code of Conduct
40+
41+
Please be respectful and professional in all interactions within this project. We aim to foster a welcoming and inclusive environment for everyone.
42+
43+
---
44+
Made with ❤️ by the Neuromechatronics Lab

paper.bib

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
@article{siegle2017open,
2+
title={Open Ephys: an open-source, plugin-based platform for multichannel electrophysiology},
3+
author={Siegle, Joshua H and L{\'o}pez, Aaron C and Patel, Tejas J and Abramov, Kirill and Ohayon, Shay and Voigts, Jakob},
4+
journal={Journal of neural engineering},
5+
volume={14},
6+
number={4},
7+
pages={045003},
8+
year={2017},
9+
publisher={IOP Publishing}
10+
}
11+
12+
@article{harris2020array,
13+
title={Array programming with {NumPy}},
14+
author={Harris, Charles R and Millman, K Jarrod and van der Walt, St{\'e}fan J and Gommers, Ralf and Virtanen, Pauli and Cournapeau, David and Wieser, Eric and Taylor, Julian and Berg, Sebastian and Smith, Nathaniel J and others},
15+
journal={Nature},
16+
volume={585},
17+
number={7825},
18+
pages={357--362},
19+
year={2020},
20+
publisher={Nature Publishing Group}
21+
}
22+
23+
@article{virtanen2020scipy,
24+
title={{SciPy} 1.0: fundamental algorithms for scientific computing in {Python}},
25+
author={Virtanen, Pauli and Gommers, Ralf and Oliphant, Travis E and Haberland, Matt and Reddy, Tyler and Cournapeau, David and Burovski, Evgeni and Peterson, Pearu and Weckesser, Warren and Bright, Jonathan and others},
26+
journal={Nature methods},
27+
volume={17},
28+
number={3},
29+
pages={261--272},
30+
year={2020},
31+
publisher={Nature Publishing Group}
32+
}
33+
34+
@article{hunter2007matplotlib,
35+
title={Matplotlib: A 2D graphics environment},
36+
author={Hunter, John D},
37+
journal={Computing in science \& engineering},
38+
volume={9},
39+
number={3},
40+
pages={90--95},
41+
year={2007},
42+
publisher={IEEE}
43+
}
44+
45+
@article{pedregosa2011scikit,
46+
title={Scikit-learn: Machine learning in {P}ython},
47+
author={Pedregosa, Fabian and Varoquaux, Ga{\"e}l and Gramfort, Alexandre and Michel, Vincent and Thirion, Bertrand and Grisel, Olivier and Blondel, Mathieu and Prettenhofer, Peter and Weiss, Ron and Dubourg, Vincent and others},
48+
journal={Journal of machine learning research},
49+
volume={12},
50+
number={Oct},
51+
pages={2825--2830},
52+
year={2011}
53+
}
54+
55+
@software{paszke2019pytorch,
56+
title={{PyTorch}: An Imperative Style, High-Performance Deep Learning Library},
57+
author={Paszke, Adam and Gross, Sam and Massa, Francisco and Lerer, Adam and Bradbury, James and Chanan, Gregory and Killeen, Trevor and Lin, Zeming and Gimelshein, Natalia and Antiga, Luca and others},
58+
booktitle={Advances in Neural Information Processing Systems 32},
59+
pages={8024--8035},
60+
year={2019}
61+
}

paper.md

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
---
2+
title: 'python-oephys: A Unified Python Toolkit for Real-Time Open Ephys Data Processing and Machine Learning'
3+
tags:
4+
- Python
5+
- electrophysiology
6+
- Open Ephys
7+
- EMG
8+
- real-time
9+
- machine learning
10+
authors:
11+
- name: Jonathan Shulgach
12+
orcid: 0009-0004-0449-9918
13+
affiliation: 1
14+
affiliations:
15+
- name: Neuromechatronics Lab, Carnegie Mellon University, Pittsburgh, PA, USA
16+
index: 1
17+
date: 26 January 2026
18+
bibliography: paper.bib
19+
---
20+
21+
# Summary
22+
23+
`python-oephys` is an open-source Python library designed to facilitate the acquisition, processing, and analysis of high-density electrophysiology data, specifically targeting the Open Ephys ecosystem. It provides a unified interface for both offline analysis of binary recordings and real-time streaming via ZeroMQ (ZMQ). Key capabilities include modular signal processing pipelines, automated channel quality assessment (QC), and integrated machine learning models (CNN-LSTM) optimized for low-latency gesture classification.
24+
25+
# Statement of need
26+
27+
The Open Ephys GUI [@siegle2017open] is a widely used platform for neural data acquisition, but researchers often face significant friction when transitioning from raw data acquisition to real-time closed-loop control or advanced offline analysis. Existing tools often handle either file I/O or real-time streaming, but rarely both in a unified, high-performance package.
28+
29+
`python-oephys` satisfies this need by providing:
30+
1. **Unified I/O**: A consistent API for both `.oebin` and `.npz` formats.
31+
2. **Real-time Integration**: Low-latency ZMQ clients that allow Python scripts and GUI applications to react to live neural streams.
32+
3. **ML-Ready Pipelines**: Pre-integrated deep learning architectures tailored for spatio-temporal neural signals, reducing the time required to build predictive models for BCIs or myoelectric control.
33+
34+
# State of the field
35+
36+
The field of neural data analysis is supported by several specialized tools. The official `open-ephys-python-tools` provide basic file loading capabilities but lack high-level processing or real-time application layers. In the domain of myoelectric control, libraries like `LibEMG` offer comprehensive pipelines but are often decoupled from the specific streaming protocols used by hardware like Open Ephys.
37+
38+
`python-oephys` bridges these domains by specializing in the high-density spatial configurations typical of Open Ephys hardware while providing the real-time application layer (viewers and decoders) missing from low-level I/O libraries. It leverages the scientific Python stack, including NumPy [@harris2020array], SciPy [@virtanen2020scipy], and Matplotlib [@hunter2007matplotlib], to provide robust data structures and visualizations.
39+
40+
# Software Design
41+
42+
`python-oephys` is designed with a modular architecture that separates data acquisition, processing, and visualization.
43+
44+
- **Interface Layer**: Implements ZMQ and LSL clients for low-latency data streaming. The `ZMQClient` is designed to run asynchronously, ensuring that data acquisition does not block processing or UI updates.
45+
- **Processing Layer**: Provides a suite of filters and feature extraction tools. This includes the `EMGPreprocessor` for standardized filtering and `ChannelQC` for real-time signal quality monitoring.
46+
- **ML Layer**: Integrated with PyTorch [@paszke2019pytorch] and scikit-learn [@pedregosa2011scikit], this layer provides pre-configured models like `EMGClassifierCNNLSTM`. These models are designed to handle the variable channel counts and sampling rates common in high-density recordings.
47+
- **Visualization Layer**: Built on PyQt5 and pyqtgraph, providing high-frame-rate real-time plots and interactive offline analysis tools.
48+
49+
# Research Impact
50+
51+
`python-oephys` has been deployed in the Neuromechatronics Lab at Carnegie Mellon University to support research in high-density EMG-based human-computer interaction and robotic control. Its ability to provide sub-millisecond latency for feature extraction has enabled more responsive myoelectric interfaces compared to previous custom implementations.
52+
53+
# Acknowledgements
54+
55+
This work was supported by the Neuromechatronics Lab at Carnegie Mellon University.
56+
57+
# References

pytest.ini

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
[pytest]
2+
pythonpath = src
3+
testpaths = tests

src/pyoephys/processing/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
extract_features_sliding_window,
3030
feature_spec_from_registry,
3131
compute_rms,
32+
calculate_rms,
3233
window_rms_1D,
3334
compute_grid_average,
3435
orthogonalize,

tests/test_processing.py

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
import pytest
2+
import numpy as np
3+
from pyoephys.processing import (
4+
bandpass_filter,
5+
notch_filter,
6+
lowpass_filter,
7+
compute_rms,
8+
calculate_rms,
9+
root_mean_square,
10+
common_average_reference
11+
)
12+
13+
def test_notch_filter():
14+
fs = 1000
15+
t = np.arange(fs) / fs
16+
# Signal with 60 Hz noise
17+
f0 = 60
18+
sig = np.sin(2 * np.pi * 10 * t) + np.sin(2 * np.pi * f0 * t)
19+
sig = sig.reshape(1, -1)
20+
21+
filtered = notch_filter(sig, fs=fs, f0=f0)
22+
23+
# Check that 60Hz is attenuated (simple check)
24+
orig_power = np.sum(sig**2)
25+
filt_power = np.sum(filtered**2)
26+
assert filt_power < orig_power
27+
28+
def test_bandpass_filter():
29+
fs = 1000
30+
t = np.arange(fs) / fs
31+
# Signal with 5Hz and 100Hz components
32+
sig = np.sin(2 * np.pi * 5 * t) + np.sin(2 * np.pi * 100 * t) + np.sin(2 * np.pi * 400 * t)
33+
sig = sig.reshape(1, -1)
34+
35+
# Bandpass 20-200Hz
36+
filtered = bandpass_filter(sig, lowcut=20, highcut=200, fs=fs)
37+
38+
assert filtered.shape == sig.shape
39+
assert not np.array_equal(filtered, sig)
40+
41+
def test_rms_computations():
42+
# Constant signal
43+
data = np.ones((1, 100)) * 2
44+
45+
# root_mean_square (flat result)
46+
val = root_mean_square(data[0])
47+
assert pytest.approx(val) == 2.0
48+
49+
# calculate_rms (non-overlapping windows)
50+
rms_windowed = calculate_rms(data, window_size=10)
51+
assert rms_windowed.shape == (1, 10)
52+
assert np.all(pytest.approx(rms_windowed) == 2.0)
53+
54+
# compute_rms (whole array)
55+
val2 = compute_rms(data[0])
56+
assert pytest.approx(val2) == 2.0
57+
58+
def test_common_average_reference():
59+
data = np.random.rand(8, 100)
60+
# Add common noise
61+
data += 10.0
62+
63+
car_data = common_average_reference(data)
64+
65+
assert car_data.shape == data.shape
66+
# Means should be close to zero after CAR
67+
assert np.all(np.abs(np.mean(car_data, axis=0)) < 1e-10)

0 commit comments

Comments
 (0)