atriakit 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- atriakit-0.1.0/LICENSE +21 -0
- atriakit-0.1.0/PKG-INFO +330 -0
- atriakit-0.1.0/README.md +295 -0
- atriakit-0.1.0/atriakit/__init__.py +65 -0
- atriakit-0.1.0/atriakit/configs/annotations_loader_config.py +24 -0
- atriakit-0.1.0/atriakit/configs/feature_computation_config.py +45 -0
- atriakit-0.1.0/atriakit/configs/pipeline_config.py +32 -0
- atriakit-0.1.0/atriakit/configs/segment_config.py +11 -0
- atriakit-0.1.0/atriakit/configs/signal_preprocessor_config.py +48 -0
- atriakit-0.1.0/atriakit/constants.py +18 -0
- atriakit-0.1.0/atriakit/datasets.py +286 -0
- atriakit-0.1.0/atriakit/feature_calculator.py +1403 -0
- atriakit-0.1.0/atriakit/features/__init__.py +0 -0
- atriakit-0.1.0/atriakit/features/amplitude.py +19 -0
- atriakit-0.1.0/atriakit/features/area.py +104 -0
- atriakit-0.1.0/atriakit/features/complexity.py +51 -0
- atriakit-0.1.0/atriakit/features/entropy.py +51 -0
- atriakit-0.1.0/atriakit/features/fragments.py +117 -0
- atriakit-0.1.0/atriakit/features/morphology.py +189 -0
- atriakit-0.1.0/atriakit/features/vcg.py +81 -0
- atriakit-0.1.0/atriakit/io/__init__.py +13 -0
- atriakit-0.1.0/atriakit/io/annotations_loader.py +223 -0
- atriakit-0.1.0/atriakit/io/base_loader.py +15 -0
- atriakit-0.1.0/atriakit/io/dicom_loader.py +66 -0
- atriakit-0.1.0/atriakit/io/ecg_loader.py +47 -0
- atriakit-0.1.0/atriakit/models/__init__.py +3 -0
- atriakit-0.1.0/atriakit/models/annotation_row.py +18 -0
- atriakit-0.1.0/atriakit/models/annotation_schema.py +25 -0
- atriakit-0.1.0/atriakit/models/annotations.py +193 -0
- atriakit-0.1.0/atriakit/models/ecg_data.py +94 -0
- atriakit-0.1.0/atriakit/preprocessing/__init__.py +9 -0
- atriakit-0.1.0/atriakit/preprocessing/annotations.py +43 -0
- atriakit-0.1.0/atriakit/preprocessing/signals.py +247 -0
- atriakit-0.1.0/atriakit/processing/dataset.py +215 -0
- atriakit-0.1.0/atriakit/processing/pipeline.py +542 -0
- atriakit-0.1.0/atriakit/processing/segment_processor.py +122 -0
- atriakit-0.1.0/atriakit/utils.py +180 -0
- atriakit-0.1.0/atriakit.egg-info/PKG-INFO +330 -0
- atriakit-0.1.0/atriakit.egg-info/SOURCES.txt +53 -0
- atriakit-0.1.0/atriakit.egg-info/dependency_links.txt +1 -0
- atriakit-0.1.0/atriakit.egg-info/requires.txt +13 -0
- atriakit-0.1.0/atriakit.egg-info/top_level.txt +1 -0
- atriakit-0.1.0/pyproject.toml +72 -0
- atriakit-0.1.0/setup.cfg +4 -0
- atriakit-0.1.0/tests/test_annotations.py +328 -0
- atriakit-0.1.0/tests/test_ecg_data.py +113 -0
- atriakit-0.1.0/tests/test_ecg_dataset.py +113 -0
- atriakit-0.1.0/tests/test_features.py +1410 -0
- atriakit-0.1.0/tests/test_integration.py +209 -0
- atriakit-0.1.0/tests/test_loaders.py +126 -0
- atriakit-0.1.0/tests/test_morphology.py +72 -0
- atriakit-0.1.0/tests/test_pipeline.py +393 -0
- atriakit-0.1.0/tests/test_segment_processor.py +131 -0
- atriakit-0.1.0/tests/test_signal_preprocessor.py +278 -0
- atriakit-0.1.0/tests/test_utils.py +33 -0
atriakit-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Nikolay Kormushev
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
atriakit-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,330 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: atriakit
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: An open-source framework for reproducible P-wave phenotyping
|
|
5
|
+
Author-email: Nikolay Kormushev <nkormushev@gmail.com>
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/nickormushev/AtriaKit
|
|
8
|
+
Project-URL: Repository, https://github.com/nickormushev/AtriaKit
|
|
9
|
+
Project-URL: Issues, https://github.com/nickormushev/AtriaKit/issues
|
|
10
|
+
Keywords: ecg,electrocardiogram,signal-processing,feature-extraction
|
|
11
|
+
Classifier: Development Status :: 3 - Alpha
|
|
12
|
+
Classifier: Intended Audience :: Science/Research
|
|
13
|
+
Classifier: Intended Audience :: Developers
|
|
14
|
+
Classifier: Operating System :: OS Independent
|
|
15
|
+
Classifier: Programming Language :: Python :: 3
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
17
|
+
Classifier: Topic :: Scientific/Engineering :: Medical Science Apps.
|
|
18
|
+
Classifier: Topic :: Scientific/Engineering :: Information Analysis
|
|
19
|
+
Requires-Python: >=3.12
|
|
20
|
+
Description-Content-Type: text/markdown
|
|
21
|
+
License-File: LICENSE
|
|
22
|
+
Requires-Dist: numba>=0.57
|
|
23
|
+
Requires-Dist: numpy>=1.24
|
|
24
|
+
Requires-Dist: nolds>=0.5
|
|
25
|
+
Requires-Dist: pandas>=2.0
|
|
26
|
+
Requires-Dist: pydantic>=2.0
|
|
27
|
+
Requires-Dist: PyYAML>=6.0
|
|
28
|
+
Requires-Dist: pydicom>=2.4
|
|
29
|
+
Requires-Dist: scipy>=1.10
|
|
30
|
+
Requires-Dist: tqdm>=4.65
|
|
31
|
+
Provides-Extra: dev
|
|
32
|
+
Requires-Dist: neurokit2>=0.2.7; extra == "dev"
|
|
33
|
+
Requires-Dist: pytest>=7.4; extra == "dev"
|
|
34
|
+
Dynamic: license-file
|
|
35
|
+
|
|
36
|
+
<p align="center">
|
|
37
|
+
<img src="https://raw.githubusercontent.com/nickormushev/AtriaKit/main/docs/logo.png" alt="AtriaKit" height="90">
|
|
38
|
+
</p>
|
|
39
|
+
|
|
40
|
+
<p align="center">
|
|
41
|
+
An Open-Source Framework for Reproducible P-Wave Phenotyping
|
|
42
|
+
</p>
|
|
43
|
+
|
|
44
|
+
<p align="center">
|
|
45
|
+
<a href="https://github.com/nickormushev/AtriaKit/actions/workflows/tests.yml">
|
|
46
|
+
<img src="https://github.com/nickormushev/AtriaKit/actions/workflows/tests.yml/badge.svg" alt="Tests">
|
|
47
|
+
</a>
|
|
48
|
+
</p>
|
|
49
|
+
|
|
50
|
+
## Quick Example
|
|
51
|
+
|
|
52
|
+
The fastest way to try AtriaKit hands-on is `examples/demo.ipynb`, which generates synthetic ECG data and runs it through the full pipeline end to end.
|
|
53
|
+
|
|
54
|
+
```python
|
|
55
|
+
from atriakit import AnnotationsLoader, Pipeline
|
|
56
|
+
|
|
57
|
+
annotations = AnnotationsLoader().load("annotations.csv")
|
|
58
|
+
pipeline = Pipeline(ecg_base_path="path/to/dicom/root")
|
|
59
|
+
|
|
60
|
+
features = pipeline.run(annotations)
|
|
61
|
+
print(features.get_df().head())
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
## Highlights
|
|
65
|
+
|
|
66
|
+
- Temporal, morphological, nonlinear, and spatial (VCG) P-wave descriptors, each with an explicit mathematical definition; see the [manuscript](#citation)
|
|
67
|
+
- Paired with [`ecg_annotator`](https://github.com/nickormushev/AtriaKit/tree/main/ecg_annotator), an interactive tool for expert P-wave boundary annotation
|
|
68
|
+
- Configuration-driven: filtering, boundary mode, and baseline correction are explicit and reproducible, not hard-coded; tune them via config classes directly in Python or a single YAML file that maps to them
|
|
69
|
+
- Batch feature computation across recordings through a standardized `Pipeline` interface
|
|
70
|
+
- Unit-tested reusable components
|
|
71
|
+
|
|
72
|
+
Atrial fibrillation (AF) is a major cause of cardiovascular morbidity, and the P-wave of the surface electrocardiogram is an important non-invasive marker of atrial remodeling and conduction heterogeneity. In practice, however, P-wave analysis is often implemented through custom or ad hoc signal-processing pipelines, which makes results harder to reproduce across studies. General ECG processing libraries exist, but they often do not provide the more specialized atrial and P-wave metrics needed for high-resolution AF research.
|
|
73
|
+
|
|
74
|
+
AtriaKit addresses that gap with a modular feature-extraction library (see [Highlights](#highlights) above), built to scale from exploratory analysis to larger clinical datasets.
|
|
75
|
+
|
|
76
|
+
## Implemented Feature Families
|
|
77
|
+
|
|
78
|
+
<p align="center">
|
|
79
|
+
<img src="https://raw.githubusercontent.com/nickormushev/AtriaKit/main/docs/Features.png" alt="Overview of AtriaKit's P-wave feature families" width="720">
|
|
80
|
+
</p>
|
|
81
|
+
|
|
82
|
+
The library currently includes:
|
|
83
|
+
|
|
84
|
+
- Duration and Dispersion
|
|
85
|
+
- Amplitude-Based P-Wave Features
|
|
86
|
+
- Area and Area-To-Duration Ratio
|
|
87
|
+
- P-Wave Terminal Force
|
|
88
|
+
- Morphology and Inflection-Point Features
|
|
89
|
+
- Fragmentation-Based Features
|
|
90
|
+
- Complexity
|
|
91
|
+
- Shannon Entropy and Sample Entropy
|
|
92
|
+
- Atrial Rate and Heart Rate
|
|
93
|
+
- Frontal and VCG Axis
|
|
94
|
+
- VCG Area and Eigenfeatures
|
|
95
|
+
- Signal-Averaged Vector-Magnitude P-Wave Amplitudes
|
|
96
|
+
|
|
97
|
+
The set is modular and can be extended with new metrics. For implementation details and mathematical definitions of each feature, see the AtriaKit manuscript ([Citation](#citation)).
|
|
98
|
+
|
|
99
|
+
## Installation
|
|
100
|
+
|
|
101
|
+
```bash
|
|
102
|
+
pip install atriakit
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
To work from a clone of this repository instead (e.g. for development), install in editable mode:
|
|
106
|
+
|
|
107
|
+
```bash
|
|
108
|
+
pip install -e .
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
For development dependencies (testing, etc.):
|
|
112
|
+
|
|
113
|
+
```bash
|
|
114
|
+
pip install -e .[dev]
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
The package requires Python 3.12+.
|
|
118
|
+
|
|
119
|
+
## Core API
|
|
120
|
+
|
|
121
|
+
The public package interface is exposed through `atriakit`:
|
|
122
|
+
|
|
123
|
+
```python
|
|
124
|
+
from atriakit import (
|
|
125
|
+
Annotations,
|
|
126
|
+
AnnotationsLoader,
|
|
127
|
+
ECGData,
|
|
128
|
+
ECGDataset,
|
|
129
|
+
ECGLoader,
|
|
130
|
+
FeatureCalculators,
|
|
131
|
+
FeatureComputationConfig,
|
|
132
|
+
Pipeline,
|
|
133
|
+
PipelineConfig,
|
|
134
|
+
SegmentConfig,
|
|
135
|
+
SignalPreprocessor,
|
|
136
|
+
SignalPreprocessorConfig,
|
|
137
|
+
)
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
Important components:
|
|
141
|
+
|
|
142
|
+
- `ECGLoader`: loads ECG waveforms from DICOM/IMA files and returns `ECGData`; register additional file types with `ECGLoader().register(extension, loader)`
|
|
143
|
+
- `ECGData`: holds the raw signal array plus lead layout, and returns preprocessed lead signals via a `SignalPreprocessor`
|
|
144
|
+
- `AnnotationsLoader`: loads a CSV or directory of annotation files into an `Annotations` object; also extensible via `.register(extension, loader)` for other annotation file formats
|
|
145
|
+
- `Annotations`: wrapper around the annotation table
|
|
146
|
+
- `FeatureCalculators`: computes ECG and VCG features for annotated segments, constructed with a `SegmentConfig`
|
|
147
|
+
- `SegmentConfig`: baseline correction mode and leading-sample skip applied to extracted segments
|
|
148
|
+
- `Pipeline`: runs batch feature extraction over a set of files, constructed with a `PipelineConfig`
|
|
149
|
+
- `PipelineConfig`: bundles everything needed to tune a `Pipeline` run: segment-boundary and baseline-correction settings, preprocessing filters for both feature and morphology signals, feature-computation parameters (via `FeatureComputationConfig`), and beat-grouping tolerance
|
|
150
|
+
- `FeatureComputationConfig`: per-call parameters for `FeatureCalculators.compute_all` (entropy, complexity, noise, morphology, and fragmentation settings)
|
|
151
|
+
- `SignalPreprocessor`: applies notch filtering, bandpass filtering, baseline correction, and normalization as a reusable preprocessing object; each step is optional and toggled independently via `SignalPreprocessorConfig`
|
|
152
|
+
- `ECGDataset`: computes per-lead mean/std across a set of files, for `SignalPreprocessorConfig`'s z-score normalization
|
|
153
|
+
|
|
154
|
+
## Usage
|
|
155
|
+
|
|
156
|
+
`Pipeline` is the main entry point: it batches feature computation across many recordings at once, matching each file to its rows in a combined annotations table (e.g. one `annotations.csv` covering a whole directory of DICOM/IMA files). Every config object ships with reasonable defaults, as in the Quick Example above: `Pipeline(ecg_base_path=...)` works with no further configuration, but you can tune it to your needs via a YAML file or by building the config objects directly in Python:
|
|
157
|
+
|
|
158
|
+
### 1. Configure via YAML (recommended)
|
|
159
|
+
|
|
160
|
+
Rather than building `PipelineConfig`/`SignalPreprocessorConfig` objects by hand, point `Pipeline.from_yaml` at a config file. See `examples/pipeline_config.yaml` for the full reference, covering paths, annotation boundary mode, preprocessing filters, and pipeline parameters (abbreviated below):
|
|
161
|
+
|
|
162
|
+
```yaml
|
|
163
|
+
ecg_base_path: ./data
|
|
164
|
+
annotations_dir: ./data
|
|
165
|
+
pipeline:
|
|
166
|
+
group_tolerance_ms: 200
|
|
167
|
+
signal_preprocessor_config:
|
|
168
|
+
lowcut: 1
|
|
169
|
+
highcut: 40
|
|
170
|
+
notch_freq: 50.0
|
|
171
|
+
feature_computation:
|
|
172
|
+
shannon_entropy_n_bins: 128
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
```python
|
|
176
|
+
pipeline, annotations = Pipeline.from_yaml("pipeline_config.yaml")
|
|
177
|
+
result = pipeline.run(annotations)
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
Required annotation columns: `file_path`, `lead`, `onset`, `offset`, `p_wave_id`. Optional columns such as `type`, `patient_id`, and `ignore` are used when available.
|
|
181
|
+
|
|
182
|
+
### 2. Or configure directly in Python
|
|
183
|
+
|
|
184
|
+
For programmatic control (e.g. sweeping parameters in a script), pass a `PipelineConfig` straight to `Pipeline`:
|
|
185
|
+
|
|
186
|
+
```python
|
|
187
|
+
from atriakit import FeatureComputationConfig, Pipeline, PipelineConfig, SignalPreprocessorConfig
|
|
188
|
+
|
|
189
|
+
pipeline = Pipeline(
|
|
190
|
+
ecg_base_path="path/to/dicom/root",
|
|
191
|
+
pipeline_config=PipelineConfig(
|
|
192
|
+
signal_preprocessor_config=SignalPreprocessorConfig(
|
|
193
|
+
lowcut=1.0, highcut=40.0, notch_freq=50.0
|
|
194
|
+
),
|
|
195
|
+
morphology_preprocessor_config=SignalPreprocessorConfig(
|
|
196
|
+
lowcut=1.0, highcut=30.0, notch_freq=50.0
|
|
197
|
+
),
|
|
198
|
+
feature_computation=FeatureComputationConfig(shannon_entropy_n_bins=128),
|
|
199
|
+
),
|
|
200
|
+
)
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
### 3. Compute features for one recording directly
|
|
204
|
+
|
|
205
|
+
Unlike `Pipeline`, `FeatureCalculators` works on a single already-loaded recording, so its annotations must be pre-filtered to that one file, since onset/offset are sample indices into that specific signal:
|
|
206
|
+
|
|
207
|
+
```python
|
|
208
|
+
from atriakit import AnnotationsLoader, ECGLoader, FeatureCalculators
|
|
209
|
+
|
|
210
|
+
ecg_data = ECGLoader().load("path/to/file.IMA")
|
|
211
|
+
annotations = AnnotationsLoader().load("annotations.csv")
|
|
212
|
+
file_annotations = annotations[annotations["file_path"] == "path/to/file.IMA"]
|
|
213
|
+
|
|
214
|
+
fc = FeatureCalculators()
|
|
215
|
+
|
|
216
|
+
area = fc.area(file_annotations, ecg_data)
|
|
217
|
+
max_amplitude = fc.max_amplitude(file_annotations, ecg_data)
|
|
218
|
+
all_features = fc.compute_all(file_annotations, ecg_data)
|
|
219
|
+
```
|
|
220
|
+
|
|
221
|
+
## Preprocessing
|
|
222
|
+
|
|
223
|
+
Signal preprocessing is intentionally separated from feature extraction.
|
|
224
|
+
|
|
225
|
+
- filter settings live in `SignalPreprocessorConfig`
|
|
226
|
+
- runtime normalization state (`mean` / `std`) lives on `SignalPreprocessor`
|
|
227
|
+
- `ECGData` delegates preprocessing to a `SignalPreprocessor`
|
|
228
|
+
- `FeatureCalculators` can use separate preprocessors for standard signal features and morphology-specific features
|
|
229
|
+
|
|
230
|
+
Example:
|
|
231
|
+
|
|
232
|
+
```python
|
|
233
|
+
from atriakit import ECGLoader, FeatureCalculators, SignalPreprocessor, SignalPreprocessorConfig
|
|
234
|
+
|
|
235
|
+
preprocessor = SignalPreprocessor(
|
|
236
|
+
SignalPreprocessorConfig(lowcut=1.0, highcut=40.0, notch_freq=50.0)
|
|
237
|
+
)
|
|
238
|
+
|
|
239
|
+
ecg_data = ECGLoader().load("path/to/file.IMA")
|
|
240
|
+
lead_ii = ecg_data.get_lead_signal("II", preprocessor=preprocessor)
|
|
241
|
+
|
|
242
|
+
# FeatureCalculators takes the same kind of preprocessor, separately for
|
|
243
|
+
# standard signal features and morphology-specific features (both optional;
|
|
244
|
+
# see Core API above for the defaults used when omitted)
|
|
245
|
+
fc = FeatureCalculators(
|
|
246
|
+
signal_preprocessor=preprocessor,
|
|
247
|
+
morphology_preprocessor=SignalPreprocessor(
|
|
248
|
+
SignalPreprocessorConfig(lowcut=1.0, highcut=30.0, notch_freq=50.0)
|
|
249
|
+
),
|
|
250
|
+
)
|
|
251
|
+
```
|
|
252
|
+
|
|
253
|
+
## Repository Layout
|
|
254
|
+
|
|
255
|
+
```text
|
|
256
|
+
AtriaKit/
|
|
257
|
+
├── atriakit/
|
|
258
|
+
│ ├── __init__.py
|
|
259
|
+
│ ├── datasets.py
|
|
260
|
+
│ ├── feature_calculator.py
|
|
261
|
+
│ ├── utils.py
|
|
262
|
+
│ ├── configs/
|
|
263
|
+
│ ├── features/
|
|
264
|
+
│ ├── io/
|
|
265
|
+
│ ├── models/
|
|
266
|
+
│ ├── preprocessing/
|
|
267
|
+
│ └── processing/
|
|
268
|
+
├── examples/
|
|
269
|
+
├── tests/
|
|
270
|
+
├── ecg_annotator/ # companion desktop annotation tool (separate package)
|
|
271
|
+
└── pyproject.toml
|
|
272
|
+
```
|
|
273
|
+
|
|
274
|
+
Main directories:
|
|
275
|
+
|
|
276
|
+
- `atriakit/`: reusable ECG feature extraction package
|
|
277
|
+
- `atriakit/io/`: DICOM/ECG loading, annotation-file loading, and waveform access
|
|
278
|
+
- `atriakit/models/`: `ECGData`, `Annotations`, and the annotation column schema
|
|
279
|
+
- `atriakit/preprocessing/`: signal and annotation preprocessing
|
|
280
|
+
- `atriakit/processing/`: dataset utilities and the batch pipeline
|
|
281
|
+
- `atriakit/features/`: individual feature implementations
|
|
282
|
+
- `atriakit/configs/`: processing and feature configuration
|
|
283
|
+
- `examples/`: demo material
|
|
284
|
+
- `tests/`: unit tests
|
|
285
|
+
- `ecg_annotator/`: standalone PyQt6 desktop app for manually annotating P-waves; a separate, independently packaged project, see its own [README](https://github.com/nickormushev/AtriaKit/blob/main/ecg_annotator/README.md)
|
|
286
|
+
|
|
287
|
+
## Testing
|
|
288
|
+
|
|
289
|
+
Run the test suite with:
|
|
290
|
+
|
|
291
|
+
```bash
|
|
292
|
+
pytest
|
|
293
|
+
```
|
|
294
|
+
|
|
295
|
+
Or run a specific test module:
|
|
296
|
+
|
|
297
|
+
```bash
|
|
298
|
+
pytest tests/test_features.py -q
|
|
299
|
+
```
|
|
300
|
+
|
|
301
|
+
## Notes
|
|
302
|
+
|
|
303
|
+
The repository favors a pragmatic research-code style, but the core package is structured to support gradual cleanup, extension, and more standardized use over time.
|
|
304
|
+
|
|
305
|
+
## Citation
|
|
306
|
+
|
|
307
|
+
<!-- TODO: fill in once the paper is public -->
|
|
308
|
+
|
|
309
|
+
If you use AtriaKit in your research, please cite:
|
|
310
|
+
|
|
311
|
+
```bibtex
|
|
312
|
+
@software{atriakit,
|
|
313
|
+
author = {Kormushev, Nikolay},
|
|
314
|
+
title = {AtriaKit: An Open-Source Framework for Reproducible P-Wave Phenotyping},
|
|
315
|
+
year = {2026},
|
|
316
|
+
url = {https://github.com/nickormushev/AtriaKit}
|
|
317
|
+
}
|
|
318
|
+
```
|
|
319
|
+
|
|
320
|
+
## License
|
|
321
|
+
|
|
322
|
+
MIT, see [LICENSE](https://github.com/nickormushev/AtriaKit/blob/main/LICENSE).
|
|
323
|
+
|
|
324
|
+
## Development Ideas
|
|
325
|
+
|
|
326
|
+
Areas that would likely improve maintainability further:
|
|
327
|
+
|
|
328
|
+
- split `feature_calculator.py` into smaller focused modules over time
|
|
329
|
+
- expand examples and API documentation
|
|
330
|
+
- add parallelisation
|
atriakit-0.1.0/README.md
ADDED
|
@@ -0,0 +1,295 @@
|
|
|
1
|
+
<p align="center">
|
|
2
|
+
<img src="https://raw.githubusercontent.com/nickormushev/AtriaKit/main/docs/logo.png" alt="AtriaKit" height="90">
|
|
3
|
+
</p>
|
|
4
|
+
|
|
5
|
+
<p align="center">
|
|
6
|
+
An Open-Source Framework for Reproducible P-Wave Phenotyping
|
|
7
|
+
</p>
|
|
8
|
+
|
|
9
|
+
<p align="center">
|
|
10
|
+
<a href="https://github.com/nickormushev/AtriaKit/actions/workflows/tests.yml">
|
|
11
|
+
<img src="https://github.com/nickormushev/AtriaKit/actions/workflows/tests.yml/badge.svg" alt="Tests">
|
|
12
|
+
</a>
|
|
13
|
+
</p>
|
|
14
|
+
|
|
15
|
+
## Quick Example
|
|
16
|
+
|
|
17
|
+
The fastest way to try AtriaKit hands-on is `examples/demo.ipynb`, which generates synthetic ECG data and runs it through the full pipeline end to end.
|
|
18
|
+
|
|
19
|
+
```python
|
|
20
|
+
from atriakit import AnnotationsLoader, Pipeline
|
|
21
|
+
|
|
22
|
+
annotations = AnnotationsLoader().load("annotations.csv")
|
|
23
|
+
pipeline = Pipeline(ecg_base_path="path/to/dicom/root")
|
|
24
|
+
|
|
25
|
+
features = pipeline.run(annotations)
|
|
26
|
+
print(features.get_df().head())
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
## Highlights
|
|
30
|
+
|
|
31
|
+
- Temporal, morphological, nonlinear, and spatial (VCG) P-wave descriptors, each with an explicit mathematical definition; see the [manuscript](#citation)
|
|
32
|
+
- Paired with [`ecg_annotator`](https://github.com/nickormushev/AtriaKit/tree/main/ecg_annotator), an interactive tool for expert P-wave boundary annotation
|
|
33
|
+
- Configuration-driven: filtering, boundary mode, and baseline correction are explicit and reproducible, not hard-coded; tune them via config classes directly in Python or a single YAML file that maps to them
|
|
34
|
+
- Batch feature computation across recordings through a standardized `Pipeline` interface
|
|
35
|
+
- Unit-tested reusable components
|
|
36
|
+
|
|
37
|
+
Atrial fibrillation (AF) is a major cause of cardiovascular morbidity, and the P-wave of the surface electrocardiogram is an important non-invasive marker of atrial remodeling and conduction heterogeneity. In practice, however, P-wave analysis is often implemented through custom or ad hoc signal-processing pipelines, which makes results harder to reproduce across studies. General ECG processing libraries exist, but they often do not provide the more specialized atrial and P-wave metrics needed for high-resolution AF research.
|
|
38
|
+
|
|
39
|
+
AtriaKit addresses that gap with a modular feature-extraction library (see [Highlights](#highlights) above), built to scale from exploratory analysis to larger clinical datasets.
|
|
40
|
+
|
|
41
|
+
## Implemented Feature Families
|
|
42
|
+
|
|
43
|
+
<p align="center">
|
|
44
|
+
<img src="https://raw.githubusercontent.com/nickormushev/AtriaKit/main/docs/Features.png" alt="Overview of AtriaKit's P-wave feature families" width="720">
|
|
45
|
+
</p>
|
|
46
|
+
|
|
47
|
+
The library currently includes:
|
|
48
|
+
|
|
49
|
+
- Duration and Dispersion
|
|
50
|
+
- Amplitude-Based P-Wave Features
|
|
51
|
+
- Area and Area-To-Duration Ratio
|
|
52
|
+
- P-Wave Terminal Force
|
|
53
|
+
- Morphology and Inflection-Point Features
|
|
54
|
+
- Fragmentation-Based Features
|
|
55
|
+
- Complexity
|
|
56
|
+
- Shannon Entropy and Sample Entropy
|
|
57
|
+
- Atrial Rate and Heart Rate
|
|
58
|
+
- Frontal and VCG Axis
|
|
59
|
+
- VCG Area and Eigenfeatures
|
|
60
|
+
- Signal-Averaged Vector-Magnitude P-Wave Amplitudes
|
|
61
|
+
|
|
62
|
+
The set is modular and can be extended with new metrics. For implementation details and mathematical definitions of each feature, see the AtriaKit manuscript ([Citation](#citation)).
|
|
63
|
+
|
|
64
|
+
## Installation
|
|
65
|
+
|
|
66
|
+
```bash
|
|
67
|
+
pip install atriakit
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
To work from a clone of this repository instead (e.g. for development), install in editable mode:
|
|
71
|
+
|
|
72
|
+
```bash
|
|
73
|
+
pip install -e .
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
For development dependencies (testing, etc.):
|
|
77
|
+
|
|
78
|
+
```bash
|
|
79
|
+
pip install -e .[dev]
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
The package requires Python 3.12+.
|
|
83
|
+
|
|
84
|
+
## Core API
|
|
85
|
+
|
|
86
|
+
The public package interface is exposed through `atriakit`:
|
|
87
|
+
|
|
88
|
+
```python
|
|
89
|
+
from atriakit import (
|
|
90
|
+
Annotations,
|
|
91
|
+
AnnotationsLoader,
|
|
92
|
+
ECGData,
|
|
93
|
+
ECGDataset,
|
|
94
|
+
ECGLoader,
|
|
95
|
+
FeatureCalculators,
|
|
96
|
+
FeatureComputationConfig,
|
|
97
|
+
Pipeline,
|
|
98
|
+
PipelineConfig,
|
|
99
|
+
SegmentConfig,
|
|
100
|
+
SignalPreprocessor,
|
|
101
|
+
SignalPreprocessorConfig,
|
|
102
|
+
)
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
Important components:
|
|
106
|
+
|
|
107
|
+
- `ECGLoader`: loads ECG waveforms from DICOM/IMA files and returns `ECGData`; register additional file types with `ECGLoader().register(extension, loader)`
|
|
108
|
+
- `ECGData`: holds the raw signal array plus lead layout, and returns preprocessed lead signals via a `SignalPreprocessor`
|
|
109
|
+
- `AnnotationsLoader`: loads a CSV or directory of annotation files into an `Annotations` object; also extensible via `.register(extension, loader)` for other annotation file formats
|
|
110
|
+
- `Annotations`: wrapper around the annotation table
|
|
111
|
+
- `FeatureCalculators`: computes ECG and VCG features for annotated segments, constructed with a `SegmentConfig`
|
|
112
|
+
- `SegmentConfig`: baseline correction mode and leading-sample skip applied to extracted segments
|
|
113
|
+
- `Pipeline`: runs batch feature extraction over a set of files, constructed with a `PipelineConfig`
|
|
114
|
+
- `PipelineConfig`: bundles everything needed to tune a `Pipeline` run: segment-boundary and baseline-correction settings, preprocessing filters for both feature and morphology signals, feature-computation parameters (via `FeatureComputationConfig`), and beat-grouping tolerance
|
|
115
|
+
- `FeatureComputationConfig`: per-call parameters for `FeatureCalculators.compute_all` (entropy, complexity, noise, morphology, and fragmentation settings)
|
|
116
|
+
- `SignalPreprocessor`: applies notch filtering, bandpass filtering, baseline correction, and normalization as a reusable preprocessing object; each step is optional and toggled independently via `SignalPreprocessorConfig`
|
|
117
|
+
- `ECGDataset`: computes per-lead mean/std across a set of files, for `SignalPreprocessorConfig`'s z-score normalization
|
|
118
|
+
|
|
119
|
+
## Usage
|
|
120
|
+
|
|
121
|
+
`Pipeline` is the main entry point: it batches feature computation across many recordings at once, matching each file to its rows in a combined annotations table (e.g. one `annotations.csv` covering a whole directory of DICOM/IMA files). Every config object ships with reasonable defaults, as in the Quick Example above: `Pipeline(ecg_base_path=...)` works with no further configuration, but you can tune it to your needs via a YAML file or by building the config objects directly in Python:
|
|
122
|
+
|
|
123
|
+
### 1. Configure via YAML (recommended)
|
|
124
|
+
|
|
125
|
+
Rather than building `PipelineConfig`/`SignalPreprocessorConfig` objects by hand, point `Pipeline.from_yaml` at a config file. See `examples/pipeline_config.yaml` for the full reference, covering paths, annotation boundary mode, preprocessing filters, and pipeline parameters (abbreviated below):
|
|
126
|
+
|
|
127
|
+
```yaml
|
|
128
|
+
ecg_base_path: ./data
|
|
129
|
+
annotations_dir: ./data
|
|
130
|
+
pipeline:
|
|
131
|
+
group_tolerance_ms: 200
|
|
132
|
+
signal_preprocessor_config:
|
|
133
|
+
lowcut: 1
|
|
134
|
+
highcut: 40
|
|
135
|
+
notch_freq: 50.0
|
|
136
|
+
feature_computation:
|
|
137
|
+
shannon_entropy_n_bins: 128
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
```python
|
|
141
|
+
pipeline, annotations = Pipeline.from_yaml("pipeline_config.yaml")
|
|
142
|
+
result = pipeline.run(annotations)
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
Required annotation columns: `file_path`, `lead`, `onset`, `offset`, `p_wave_id`. Optional columns such as `type`, `patient_id`, and `ignore` are used when available.
|
|
146
|
+
|
|
147
|
+
### 2. Or configure directly in Python
|
|
148
|
+
|
|
149
|
+
For programmatic control (e.g. sweeping parameters in a script), pass a `PipelineConfig` straight to `Pipeline`:
|
|
150
|
+
|
|
151
|
+
```python
|
|
152
|
+
from atriakit import FeatureComputationConfig, Pipeline, PipelineConfig, SignalPreprocessorConfig
|
|
153
|
+
|
|
154
|
+
pipeline = Pipeline(
|
|
155
|
+
ecg_base_path="path/to/dicom/root",
|
|
156
|
+
pipeline_config=PipelineConfig(
|
|
157
|
+
signal_preprocessor_config=SignalPreprocessorConfig(
|
|
158
|
+
lowcut=1.0, highcut=40.0, notch_freq=50.0
|
|
159
|
+
),
|
|
160
|
+
morphology_preprocessor_config=SignalPreprocessorConfig(
|
|
161
|
+
lowcut=1.0, highcut=30.0, notch_freq=50.0
|
|
162
|
+
),
|
|
163
|
+
feature_computation=FeatureComputationConfig(shannon_entropy_n_bins=128),
|
|
164
|
+
),
|
|
165
|
+
)
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
### 3. Compute features for one recording directly
|
|
169
|
+
|
|
170
|
+
Unlike `Pipeline`, `FeatureCalculators` works on a single already-loaded recording, so its annotations must be pre-filtered to that one file, since onset/offset are sample indices into that specific signal:
|
|
171
|
+
|
|
172
|
+
```python
|
|
173
|
+
from atriakit import AnnotationsLoader, ECGLoader, FeatureCalculators
|
|
174
|
+
|
|
175
|
+
ecg_data = ECGLoader().load("path/to/file.IMA")
|
|
176
|
+
annotations = AnnotationsLoader().load("annotations.csv")
|
|
177
|
+
file_annotations = annotations[annotations["file_path"] == "path/to/file.IMA"]
|
|
178
|
+
|
|
179
|
+
fc = FeatureCalculators()
|
|
180
|
+
|
|
181
|
+
area = fc.area(file_annotations, ecg_data)
|
|
182
|
+
max_amplitude = fc.max_amplitude(file_annotations, ecg_data)
|
|
183
|
+
all_features = fc.compute_all(file_annotations, ecg_data)
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
## Preprocessing
|
|
187
|
+
|
|
188
|
+
Signal preprocessing is intentionally separated from feature extraction.
|
|
189
|
+
|
|
190
|
+
- filter settings live in `SignalPreprocessorConfig`
|
|
191
|
+
- runtime normalization state (`mean` / `std`) lives on `SignalPreprocessor`
|
|
192
|
+
- `ECGData` delegates preprocessing to a `SignalPreprocessor`
|
|
193
|
+
- `FeatureCalculators` can use separate preprocessors for standard signal features and morphology-specific features
|
|
194
|
+
|
|
195
|
+
Example:
|
|
196
|
+
|
|
197
|
+
```python
|
|
198
|
+
from atriakit import ECGLoader, FeatureCalculators, SignalPreprocessor, SignalPreprocessorConfig
|
|
199
|
+
|
|
200
|
+
preprocessor = SignalPreprocessor(
|
|
201
|
+
SignalPreprocessorConfig(lowcut=1.0, highcut=40.0, notch_freq=50.0)
|
|
202
|
+
)
|
|
203
|
+
|
|
204
|
+
ecg_data = ECGLoader().load("path/to/file.IMA")
|
|
205
|
+
lead_ii = ecg_data.get_lead_signal("II", preprocessor=preprocessor)
|
|
206
|
+
|
|
207
|
+
# FeatureCalculators takes the same kind of preprocessor, separately for
|
|
208
|
+
# standard signal features and morphology-specific features (both optional;
|
|
209
|
+
# see Core API above for the defaults used when omitted)
|
|
210
|
+
fc = FeatureCalculators(
|
|
211
|
+
signal_preprocessor=preprocessor,
|
|
212
|
+
morphology_preprocessor=SignalPreprocessor(
|
|
213
|
+
SignalPreprocessorConfig(lowcut=1.0, highcut=30.0, notch_freq=50.0)
|
|
214
|
+
),
|
|
215
|
+
)
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
## Repository Layout
|
|
219
|
+
|
|
220
|
+
```text
|
|
221
|
+
AtriaKit/
|
|
222
|
+
├── atriakit/
|
|
223
|
+
│ ├── __init__.py
|
|
224
|
+
│ ├── datasets.py
|
|
225
|
+
│ ├── feature_calculator.py
|
|
226
|
+
│ ├── utils.py
|
|
227
|
+
│ ├── configs/
|
|
228
|
+
│ ├── features/
|
|
229
|
+
│ ├── io/
|
|
230
|
+
│ ├── models/
|
|
231
|
+
│ ├── preprocessing/
|
|
232
|
+
│ └── processing/
|
|
233
|
+
├── examples/
|
|
234
|
+
├── tests/
|
|
235
|
+
├── ecg_annotator/ # companion desktop annotation tool (separate package)
|
|
236
|
+
└── pyproject.toml
|
|
237
|
+
```
|
|
238
|
+
|
|
239
|
+
Main directories:
|
|
240
|
+
|
|
241
|
+
- `atriakit/`: reusable ECG feature extraction package
|
|
242
|
+
- `atriakit/io/`: DICOM/ECG loading, annotation-file loading, and waveform access
|
|
243
|
+
- `atriakit/models/`: `ECGData`, `Annotations`, and the annotation column schema
|
|
244
|
+
- `atriakit/preprocessing/`: signal and annotation preprocessing
|
|
245
|
+
- `atriakit/processing/`: dataset utilities and the batch pipeline
|
|
246
|
+
- `atriakit/features/`: individual feature implementations
|
|
247
|
+
- `atriakit/configs/`: processing and feature configuration
|
|
248
|
+
- `examples/`: demo material
|
|
249
|
+
- `tests/`: unit tests
|
|
250
|
+
- `ecg_annotator/`: standalone PyQt6 desktop app for manually annotating P-waves; a separate, independently packaged project, see its own [README](https://github.com/nickormushev/AtriaKit/blob/main/ecg_annotator/README.md)
|
|
251
|
+
|
|
252
|
+
## Testing
|
|
253
|
+
|
|
254
|
+
Run the test suite with:
|
|
255
|
+
|
|
256
|
+
```bash
|
|
257
|
+
pytest
|
|
258
|
+
```
|
|
259
|
+
|
|
260
|
+
Or run a specific test module:
|
|
261
|
+
|
|
262
|
+
```bash
|
|
263
|
+
pytest tests/test_features.py -q
|
|
264
|
+
```
|
|
265
|
+
|
|
266
|
+
## Notes
|
|
267
|
+
|
|
268
|
+
The repository favors a pragmatic research-code style, but the core package is structured to support gradual cleanup, extension, and more standardized use over time.
|
|
269
|
+
|
|
270
|
+
## Citation
|
|
271
|
+
|
|
272
|
+
<!-- TODO: fill in once the paper is public -->
|
|
273
|
+
|
|
274
|
+
If you use AtriaKit in your research, please cite:
|
|
275
|
+
|
|
276
|
+
```bibtex
|
|
277
|
+
@software{atriakit,
|
|
278
|
+
author = {Kormushev, Nikolay},
|
|
279
|
+
title = {AtriaKit: An Open-Source Framework for Reproducible P-Wave Phenotyping},
|
|
280
|
+
year = {2026},
|
|
281
|
+
url = {https://github.com/nickormushev/AtriaKit}
|
|
282
|
+
}
|
|
283
|
+
```
|
|
284
|
+
|
|
285
|
+
## License
|
|
286
|
+
|
|
287
|
+
MIT, see [LICENSE](https://github.com/nickormushev/AtriaKit/blob/main/LICENSE).
|
|
288
|
+
|
|
289
|
+
## Development Ideas
|
|
290
|
+
|
|
291
|
+
Areas that would likely improve maintainability further:
|
|
292
|
+
|
|
293
|
+
- split `feature_calculator.py` into smaller focused modules over time
|
|
294
|
+
- expand examples and API documentation
|
|
295
|
+
- add parallelisation
|