Modular toolkit for single-molecule nanopore electrophysiology analysis
Project description
Pynanopore
Production-oriented toolkit for single-molecule nanopore electrophysiology analysis.
It provides:
- A shared Python library (
pynanopore) for I/O, event detection, dwell-time fitting, and PSD analysis - FastAPI microservices for those capabilities
- An API gateway and Streamlit UI
- Docker Compose orchestration for local / production-like runs
Science Phase E (multi-level events, percentile baseline, multi-Lorentzian PSD, parallel batch): see docs/science_phase_e.md.
First analysis tutorial: docs/first_analysis.md.
Event detection math (thresholds, baseline, pulse-shape idealization): see docs/event_detection_math.md.
Dwell-time lifetime fitting (MLE, AIC/BIC): see docs/dwelltime_math.md.
PSD models (Welch, Lorentzian, composite (1/f)): see docs/psd_math.md.
Batch multi-file pipelines: see docs/batch_analysis.md.
Changelog / releasing: CHANGELOG.md · docs/releasing.md.
Install (2-minute try)
Library + CLI (Python ≥ 3.10):
pip install "pynanopore[viz]"
pynanopore --version
pynanopore detect path/to/recording.csv -o events.csv
Use the shipped example after cloning:
git clone https://github.com/jaybfn/Single-Molecule-Electrophysiology-Data-Analysis.git
cd Single-Molecule-Electrophysiology-Data-Analysis
pip install -e ".[viz]"
pynanopore detect data/test.csv -o events.csv
pynanopore dwelltime events.csv --fit single --method mle
pynanopore psd data/test.csv --fit
Full stack (UI + APIs):
docker compose up --build
Then open http://localhost:8501 and click Use example CSV. Walkthrough: docs/first_analysis.md.
Architecture
Pynanopore is split into a shared scientific core and stateless HTTP microservices. Clients never call analysis services directly in the Compose deployment: they talk to the gateway (BFF), which routes requests, aggregates health, and keeps service URLs internal.
System overview
flowchart TB
subgraph clients [Clients]
UI["web-ui<br/>Streamlit :8501"]
CLI["pynanopore CLI"]
HTTP["HTTP / OpenAPI clients"]
end
subgraph compose [Docker Compose network]
GW["gateway<br/>FastAPI BFF :8000"]
subgraph analysis [Analysis microservices]
EVT["event-service :8001<br/>detect translocation events"]
STAT["stats-service :8002<br/>dwell-time histogram + fit"]
PSD["psd-service :8003<br/>Welch PSD + Lorentzian"]
end
CORE["pynanopore core library<br/>io · detection · dwelltime · psd · viz"]
end
UI -->|"HTTP JSON / multipart"| GW
CLI -->|"optional local core<br/>or HTTP via gateway"| GW
HTTP --> GW
GW -->|"POST /v1/detect"| EVT
GW -->|"POST /v1/dwelltime"| STAT
GW -->|"POST /v1/psd<br/>POST /v1/psd/upload"| PSD
EVT --> CORE
STAT --> CORE
PSD --> CORE
How services communicate
| Hop | Protocol | Payload | Purpose |
|---|---|---|---|
web-ui → gateway |
HTTP | multipart file or JSON | Single public entrypoint |
gateway → event-service |
HTTP proxy | ABF/CSV upload | Preview downsample or threshold event detection |
gateway → stats-service |
HTTP proxy | JSON list of events | Fit dwell-time distributions |
gateway → psd-service |
HTTP proxy | current array or file | Estimate PSD / fit Lorentzian |
| each analysis service → core | in-process Python import | NumPy / pandas objects | Shared algorithms (no RPC inside core) |
Typical interactive workflow:
- User uploads a recording (or loads the example CSV) in web-ui.
- UI calls
POST /v1/previewfor a downsampled trace and draws live threshold lines. - UI calls
POST /v1/detect→ event-service returns events (+ optional Plotly / preview). - UI sends those events to
POST /v1/dwelltime→ stats-service. - UI sends preview current (or re-uploads) to
POST /v1/psd→ psd-service. - User downloads CSV / JSON / plot HTML·PNG exports from each tab.
GET /health on the gateway probes each downstream /health endpoint and reports ok / degraded / down.
Service responsibilities
| Service | Port | Responsibility |
|---|---|---|
gateway |
8000 | BFF: routing, validation pass-through, health aggregation |
event-service |
8001 | Load ABF/CSV → chunked threshold event detection |
stats-service |
8002 | Dwell-time histogram + single/double exponential fit |
psd-service |
8003 | Welch PSD + Lorentzian power-1 fit |
web-ui |
8501 | Streamlit client (calls gateway only; no local analysis) |
Request sequence (event → stats → PSD)
sequenceDiagram
participant UI as web-ui
participant GW as gateway
participant EVT as event-service
participant STAT as stats-service
participant PSD as psd-service
participant CORE as pynanopore core
UI->>GW: POST /v1/detect (file)
GW->>EVT: proxy multipart upload
EVT->>CORE: load_trace + EventDetector
CORE-->>EVT: events, preview arrays
EVT-->>GW: DetectResponse JSON
GW-->>UI: events + optional plot
UI->>GW: POST /v1/dwelltime (events JSON)
GW->>STAT: proxy JSON
STAT->>CORE: DwellTimeExponentialFit
CORE-->>STAT: params, hist, fitted curve
STAT-->>GW: StatsResponse JSON
GW-->>UI: fit parameters + optional plot
UI->>GW: POST /v1/psd (current, fs)
GW->>PSD: proxy JSON
PSD->>CORE: PSDAnalyzer + LorentzianFitter
CORE-->>PSD: frequencies, S0, fc
PSD-->>GW: PSDResponse JSON
GW-->>UI: PSD + Lorentzian params
Event detection mathematics
Full derivation, parameter guidance, and pulse-shape idealization are documented in docs/event_detection_math.md.
Summary: events are found with a dual-threshold rule on a baseline-corrected residual (canonicalized so events are downward), then characterized with (I_0), (\Delta I/I_0), area, and optional rectangular pulse idealization for visualization.
1. Baseline statistics
For a chunk of (N) samples:
[ \mu = \frac{1}{N}\sum_{n=1}^{N} I[n], \qquad \sigma = \sqrt{\frac{1}{N}\sum_{n=1}^{N}\bigl(I[n]-\mu\bigr)^{2}} ]
Two downward thresholds are defined from user multipliers (k_{\mathrm{std}}) (std_multiplier) and (k_{\mathrm{thr}}) (threshold_multiplier):
[ T_{\mathrm{entry}} = \mu - k_{\mathrm{std}},\sigma \qquad T_{\mathrm{deep}} = \mu - k_{\mathrm{thr}},\sigma ]
Defaults are (k_{\mathrm{std}}=0.25) and (k_{\mathrm{thr}}=1.5), so (T_{\mathrm{deep}} < T_{\mathrm{entry}} < \mu) when (\sigma>0).
(T_{\mathrm{entry}}) is a sensitive entry/exit level; (T_{\mathrm{deep}}) is a confirmation level that rejects shallow noise spikes.
current I
^
| ──────── μ (open pore mean)
| · · · · T_entry = μ − k_std σ ← start / end crossings
| · · · T_deep = μ − k_thr σ ← must be reached inside event
| ╲___╱ blockade
+------------------------------→ time
2. State-machine detection rule
Scanning samples (n = 1\ldots N-1):
- Start candidate when the signal crosses below the entry threshold:
[ I[n-1] \ge T_{\mathrm{entry}} \quad\text{and}\quad I[n] < T_{\mathrm{entry}} ]
Record (t_{\mathrm{start}} = t[n]) and index (n_{\mathrm{start}}).
- Confirm depth if, while a candidate is open,
[ I[n] < T_{\mathrm{deep}} ]
set a flag that the deep threshold was reached.
- End candidate when the signal crosses back above the entry threshold:
[ I[n-1] < T_{\mathrm{entry}} \quad\text{and}\quad I[n] \ge T_{\mathrm{entry}} ]
- Accept event only if the deep threshold was reached and dwell time meets the minimum:
[ \tau = t_{\mathrm{end}} - t_{\mathrm{start}} \ge \tau_{\min} \qquad (\tau_{\min}=10^{-4},\mathrm{s}\ \text{by default}) ]
For each accepted event the amplitude is the deepest current in the segment:
[ A = \min_{n \in [n_{\mathrm{start}},, n_{\mathrm{end}}]} I[n] ]
Stored fields: start_time, end_time, difference ((=\tau)), amplitude ((A)).
3. Chunking long recordings
Long traces are split into windows of length (\Delta t) seconds (interval_length, default 5 s). With sample rate (f_s):
[ N_{\mathrm{chunk}} = \lfloor f_s \cdot \Delta t \rfloor ]
Detection runs independently on each chunk; events are concatenated. Thresholds (\mu,\sigma) are local to each chunk, which adapts to slow baseline drift but can bias events that straddle chunk boundaries.
4. Why two thresholds?
A single threshold tends to either (a) miss shallow true events or (b) accept noise. The dual-threshold rule is a simple hysteresis-style criterion:
- cross (T_{\mathrm{entry}}) to mark possible on/off times with high sensitivity;
- require a visit below (T_{\mathrm{deep}}) so only excursions with sufficient blockade depth are kept.
This is the logic implemented in EventDetector (packages/pynanopore/src/pynanopore/detection/events.py).
Install (library + CLI)
Requires Python 3.10+.
pip install -e ".[dev,viz,services]"
Optional extras:
viz— Plotly helpersui— Streamlit client depsservices— FastAPI / uvicorndev— pytest, ruff, mypy, pre-commit
CLI
pynanopore detect path/to/file.abf -o events.csv
pynanopore dwelltime events.csv --fit single --bins 50
pynanopore psd path/to/file.abf --fit
Library usage
from pynanopore import load_trace, EventDetector, DwellTimeExponentialFit, PSDAnalyzer
trace = load_trace("recording.abf")
events = EventDetector(0.25, 1.5).detect_trace(trace)
Run microservices (Docker Compose)
docker compose up --build
- UI: http://localhost:8501
- Gateway OpenAPI: http://localhost:8000/docs
- Event service docs: http://localhost:8001/docs
Gateway and analysis services wait on Docker healthchecks before dependents start. Useful env vars (compose defaults shown):
| Variable | Default | Purpose |
|---|---|---|
LOG_LEVEL |
INFO |
Service log level |
LOG_JSON |
true |
Structured JSON logs |
MAX_UPLOAD_MB |
100 |
Reject larger uploads with HTTP 413 |
HTTP_TIMEOUT_S |
120 |
General HTTP timeout setting |
DOWNSTREAM_TIMEOUT_S |
120 |
Gateway → service call timeout |
All HTTP responses include X-Request-ID (pass one in to correlate gateway → services). Errors use { "error": { "code", "message", "request_id", ... } }.
Pinned dependencies for reproducible installs: requirements.lock (use as pip install ... -c requirements.lock). Regenerate with:
pip-compile --strip-extras --extra viz --extra ui --extra services --extra dev -o requirements.lock pyproject.toml
Gateway API overview
GET /health— gateway + downstream healthPOST /v1/preview— multipart upload → downsampled trace for UI tuningPOST /v1/detect— multipart file upload → eventsPOST /v1/dwelltime— JSON events → fit paramsPOST /v1/psd— JSON current array → PSD (+ optional Lorentzian)POST /v1/psd/upload— file upload → PSD
Development
pip install -e ".[dev,viz,services]"
pre-commit install
ruff check packages services tests
mypy packages/pynanopore/src/pynanopore
pytest
Project layout
packages/pynanopore/src/pynanopore/ # core library
services/
event-service/
stats-service/
psd-service/
gateway/
web-ui/
tests/
docker-compose.yml
pyproject.toml
CI / releases
Push/PR to main runs lint, typecheck, tests, and wheel build.
Annotated tags matching v* (e.g. v2.7.0) run the same quality job, then:
- PyPI — Trusted Publishing (
pypa/gh-action-pypi-publish) - Docker Hub — gateway, event-service, stats-service, psd-service, web-ui (
:latestand:$TAG) whenDOCKER_USERNAME/DOCKER_PASSWORDare set
See docs/releasing.md.
License
MIT — see LICENSE.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file pynanopore-2.7.0.tar.gz.
File metadata
- Download URL: pynanopore-2.7.0.tar.gz
- Upload date:
- Size: 32.3 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
b689da8bab1e5b54ef1d2c7b647587c7b14c1a553825702262b750019343028a
|
|
| MD5 |
ec5219eb76a250faacc32ec6a8eab1c8
|
|
| BLAKE2b-256 |
e856a8857b376d5f7f4a02db4376a50af3c370b9b7026aad532da2c46ccbc28d
|
Provenance
The following attestation bundles were made for pynanopore-2.7.0.tar.gz:
Publisher:
ci.yml on jaybfn/Single-Molecule-Electrophysiology-Data-Analysis
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
pynanopore-2.7.0.tar.gz -
Subject digest:
b689da8bab1e5b54ef1d2c7b647587c7b14c1a553825702262b750019343028a - Sigstore transparency entry: 2287497723
- Sigstore integration time:
-
Permalink:
jaybfn/Single-Molecule-Electrophysiology-Data-Analysis@c6a0f78fbcac652a6717bf60504f6b33caf71dd1 -
Branch / Tag:
refs/tags/v2.7.0 - Owner: https://github.com/jaybfn
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
ci.yml@c6a0f78fbcac652a6717bf60504f6b33caf71dd1 -
Trigger Event:
push
-
Statement type:
File details
Details for the file pynanopore-2.7.0-py3-none-any.whl.
File metadata
- Download URL: pynanopore-2.7.0-py3-none-any.whl
- Upload date:
- Size: 41.6 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
bb5293c0a74e2fa1567bcc9e670b4f653432284a592e2df6e4e498c891d3e446
|
|
| MD5 |
b5cb970e68aaa17c19c4906b231aad76
|
|
| BLAKE2b-256 |
61948dcbd447467db73be2adabd1c0624df9034829d9221ef50a705d57686464
|
Provenance
The following attestation bundles were made for pynanopore-2.7.0-py3-none-any.whl:
Publisher:
ci.yml on jaybfn/Single-Molecule-Electrophysiology-Data-Analysis
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
pynanopore-2.7.0-py3-none-any.whl -
Subject digest:
bb5293c0a74e2fa1567bcc9e670b4f653432284a592e2df6e4e498c891d3e446 - Sigstore transparency entry: 2287497754
- Sigstore integration time:
-
Permalink:
jaybfn/Single-Molecule-Electrophysiology-Data-Analysis@c6a0f78fbcac652a6717bf60504f6b33caf71dd1 -
Branch / Tag:
refs/tags/v2.7.0 - Owner: https://github.com/jaybfn
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
ci.yml@c6a0f78fbcac652a6717bf60504f6b33caf71dd1 -
Trigger Event:
push
-
Statement type: