pmecg 0.2.3__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.
- pmecg-0.2.3/.github/copilot-instructions.md +63 -0
- pmecg-0.2.3/.github/workflows/ci.yml +95 -0
- pmecg-0.2.3/.github/workflows/release.yml +95 -0
- pmecg-0.2.3/.gitignore +40 -0
- pmecg-0.2.3/.python-version +1 -0
- pmecg-0.2.3/.readthedocs.yml +14 -0
- pmecg-0.2.3/LICENSE +674 -0
- pmecg-0.2.3/PKG-INFO +83 -0
- pmecg-0.2.3/README.md +51 -0
- pmecg-0.2.3/assets/logo-dark.svg +16 -0
- pmecg-0.2.3/assets/logo.svg +16 -0
- pmecg-0.2.3/docs/_config.yml +69 -0
- pmecg-0.2.3/docs/_toc.yml +14 -0
- pmecg-0.2.3/docs/api/index.md +32 -0
- pmecg-0.2.3/docs/examples/attention.md +238 -0
- pmecg-0.2.3/docs/examples/basic.md +72 -0
- pmecg-0.2.3/docs/examples/configurations.md +202 -0
- pmecg-0.2.3/docs/examples/index.md +5 -0
- pmecg-0.2.3/docs/intro.md +56 -0
- pmecg-0.2.3/example/artifacts/attention/4x3-background-positive.pdf +0 -0
- pmecg-0.2.3/example/artifacts/attention/4x3-background-positive.png +0 -0
- pmecg-0.2.3/example/artifacts/attention/4x3-background-signed.pdf +0 -0
- pmecg-0.2.3/example/artifacts/attention/4x3-background-signed.png +0 -0
- pmecg-0.2.3/example/artifacts/attention/4x3-interval-positive.pdf +0 -0
- pmecg-0.2.3/example/artifacts/attention/4x3-interval-positive.png +0 -0
- pmecg-0.2.3/example/artifacts/attention/4x3-interval-signed.pdf +0 -0
- pmecg-0.2.3/example/artifacts/attention/4x3-interval-signed.png +0 -0
- pmecg-0.2.3/example/artifacts/attention/4x3-line-color-positive.pdf +0 -0
- pmecg-0.2.3/example/artifacts/attention/4x3-line-color-positive.png +0 -0
- pmecg-0.2.3/example/artifacts/attention/4x3-line-color-signed.pdf +0 -0
- pmecg-0.2.3/example/artifacts/attention/4x3-line-color-signed.png +0 -0
- pmecg-0.2.3/example/artifacts/attention/manual-background.pdf +0 -0
- pmecg-0.2.3/example/artifacts/attention/manual-background.png +0 -0
- pmecg-0.2.3/example/artifacts/attention/manual-interval.pdf +0 -0
- pmecg-0.2.3/example/artifacts/attention/manual-interval.png +0 -0
- pmecg-0.2.3/example/artifacts/attention/manual-line-color.pdf +0 -0
- pmecg-0.2.3/example/artifacts/attention/manual-line-color.png +0 -0
- pmecg-0.2.3/example/artifacts/no-attention/1/1x3.pdf +0 -0
- pmecg-0.2.3/example/artifacts/no-attention/1/1x3.png +0 -0
- pmecg-0.2.3/example/artifacts/no-attention/1/2x6.pdf +0 -0
- pmecg-0.2.3/example/artifacts/no-attention/1/2x6.png +0 -0
- pmecg-0.2.3/example/artifacts/no-attention/1/4x3.pdf +0 -0
- pmecg-0.2.3/example/artifacts/no-attention/1/4x3.png +0 -0
- pmecg-0.2.3/example/artifacts/no-attention/2/1x3.pdf +0 -0
- pmecg-0.2.3/example/artifacts/no-attention/2/1x3.png +0 -0
- pmecg-0.2.3/example/artifacts/no-attention/2/2x6.pdf +0 -0
- pmecg-0.2.3/example/artifacts/no-attention/2/2x6.png +0 -0
- pmecg-0.2.3/example/artifacts/no-attention/2/4x3.pdf +0 -0
- pmecg-0.2.3/example/artifacts/no-attention/2/4x3.png +0 -0
- pmecg-0.2.3/example/artifacts/no-attention/3/1x3.pdf +0 -0
- pmecg-0.2.3/example/artifacts/no-attention/3/1x3.png +0 -0
- pmecg-0.2.3/example/artifacts/no-attention/3/2x6.pdf +0 -0
- pmecg-0.2.3/example/artifacts/no-attention/3/2x6.png +0 -0
- pmecg-0.2.3/example/artifacts/no-attention/3/4x3.pdf +0 -0
- pmecg-0.2.3/example/artifacts/no-attention/3/4x3.png +0 -0
- pmecg-0.2.3/example/artifacts/no-attention/4/1x3.pdf +0 -0
- pmecg-0.2.3/example/artifacts/no-attention/4/1x3.png +0 -0
- pmecg-0.2.3/example/artifacts/no-attention/4/2x6.pdf +0 -0
- pmecg-0.2.3/example/artifacts/no-attention/4/2x6.png +0 -0
- pmecg-0.2.3/example/artifacts/no-attention/4/4x3.pdf +0 -0
- pmecg-0.2.3/example/artifacts/no-attention/4/4x3.png +0 -0
- pmecg-0.2.3/example/artifacts/no-attention/5/1x3.pdf +649 -0
- pmecg-0.2.3/example/artifacts/no-attention/5/1x3.png +0 -0
- pmecg-0.2.3/example/artifacts/no-attention/5/2x6.pdf +0 -0
- pmecg-0.2.3/example/artifacts/no-attention/5/2x6.png +0 -0
- pmecg-0.2.3/example/artifacts/no-attention/5/4x3.pdf +0 -0
- pmecg-0.2.3/example/artifacts/no-attention/5/4x3.png +0 -0
- pmecg-0.2.3/pixi.lock +16652 -0
- pmecg-0.2.3/pyproject.toml +150 -0
- pmecg-0.2.3/src/pmecg/__init__.py +37 -0
- pmecg-0.2.3/src/pmecg/plot.py +387 -0
- pmecg-0.2.3/src/pmecg/py.typed +0 -0
- pmecg-0.2.3/src/pmecg/types.py +79 -0
- pmecg-0.2.3/src/pmecg/utils/__init__.py +0 -0
- pmecg-0.2.3/src/pmecg/utils/attention.py +805 -0
- pmecg-0.2.3/src/pmecg/utils/data.py +421 -0
- pmecg-0.2.3/src/pmecg/utils/plot.py +563 -0
- pmecg-0.2.3/tests/output_helpers.py +36 -0
- pmecg-0.2.3/tests/ptbxl_helper.py +98 -0
- pmecg-0.2.3/tests/test_attention.py +1156 -0
- pmecg-0.2.3/tests/test_attention_artifacts.py +170 -0
- pmecg-0.2.3/tests/test_data.py +442 -0
- pmecg-0.2.3/tests/test_plot_systematic.py +919 -0
- pmecg-0.2.3/tests/test_ptbxl_artifacts.py +64 -0
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
# pmecg — Copilot Instructions
|
|
2
|
+
|
|
3
|
+
`pmecg` is a Python library for plotting high-quality, paper-like ECG signals using Matplotlib.
|
|
4
|
+
|
|
5
|
+
## Commands
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
pixi install # Install the default development environment
|
|
9
|
+
pixi run test # Run all tests in the default environment
|
|
10
|
+
pixi run test-fast # Unit + structural tests only (no network)
|
|
11
|
+
pixi run pytest tests/test_data.py::TestSegmentLeads -v # Run a specific test class
|
|
12
|
+
pixi run pytest tests/test_data.py::TestNumpyToDataframe::test_shape -v # Run a single test
|
|
13
|
+
pixi run lint # Lint + formatting check
|
|
14
|
+
pixi run ruff check . --fix # Lint with auto-fix
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
Always use `pixi run` (or named Pixi tasks) to invoke Python tools — never `python` or `pytest` directly.
|
|
18
|
+
|
|
19
|
+
Integration tests (`@pytest.mark.integration`) require network access to download the PTB-XL dataset and are slow; skip them with `-m "not integration"` for routine development.
|
|
20
|
+
|
|
21
|
+
## Architecture
|
|
22
|
+
|
|
23
|
+
The codebase has three layers:
|
|
24
|
+
|
|
25
|
+
1. **Public API** (`src/pmecg/plot.py`, re-exported via `src/pmecg/__init__.py`):
|
|
26
|
+
- `ECGPlotter` — main class; instantiate with visual parameters, then call `.plot()`
|
|
27
|
+
- `ECGStats`, `ECGInformation` — dataclasses for optional metadata overlays
|
|
28
|
+
- `template_factory()`, `LeadsMap`, `SUPPORTED_LEADS` — configuration helpers
|
|
29
|
+
|
|
30
|
+
2. **Data layer** (`src/pmecg/utils/data.py`):
|
|
31
|
+
- Normalizes ECG input (numpy arrays, lists of arrays, or DataFrames) into a DataFrame
|
|
32
|
+
- Resolves and validates configuration (template expansion, lead name mapping)
|
|
33
|
+
- Segments leads into rows for rendering
|
|
34
|
+
|
|
35
|
+
3. **Rendering layer** (`src/pmecg/utils/plot.py`):
|
|
36
|
+
- Low-level Matplotlib logic: grid drawing, figure sizing, calibration pulse, row plotting
|
|
37
|
+
- Uses `MM_PER_INCH = 25.4` and physical-unit constants (mm) for exact paper sizes
|
|
38
|
+
|
|
39
|
+
Data flows: `ECGPlotter.plot()` → data layer normalizes input and expands config → rendering layer produces a Matplotlib `Figure`.
|
|
40
|
+
|
|
41
|
+
## Key Conventions
|
|
42
|
+
|
|
43
|
+
**Configuration system:**
|
|
44
|
+
- A *configuration* is `list[list[str] | str]` — each element is a row; a string is a full-width lead, a sublist is concatenated leads sharing a row.
|
|
45
|
+
- Built-in templates (`"4x3"`, `"2x6"`, `"1x12"`, etc.) must be expanded via `template_factory()` before passing to `ECGPlotter.plot()` — `plot()` does not accept raw template strings.
|
|
46
|
+
- Custom lead names are mapped to canonical names (`"I"`, `"II"`, ..., `"V6"`) via `LeadsMap`.
|
|
47
|
+
|
|
48
|
+
**Types:**
|
|
49
|
+
```python
|
|
50
|
+
ECGDataType = tuple[list[np.ndarray] | np.ndarray, list[str]] | pd.DataFrame
|
|
51
|
+
ConfigurationDataType = list[list[str] | str]
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
**Naming:**
|
|
55
|
+
- Public symbols: `PascalCase` (`ECGPlotter`, `LeadsMap`)
|
|
56
|
+
- Internal helpers: leading underscore (`_segment_leads`, `_plot_row`, `_RenderContext`)
|
|
57
|
+
- Module-level constants: `UPPER_CASE` (`MM_PER_INCH`, `SUPPORTED_LEADS`, `CAL_PULSE_AMP_MV`)
|
|
58
|
+
|
|
59
|
+
**Typing:** Full type annotations are required throughout (`py.typed` marker is present). Docstrings follow NumPy/SciPy style.
|
|
60
|
+
|
|
61
|
+
**Linting:** Ruff with rules `E, W, F, I, UP, B` and line length 128, targeting Python 3.8+. The `pixi run lint` task runs `ruff check . && ruff format --check .`.
|
|
62
|
+
|
|
63
|
+
**Tests:** `test_plot_systematic.py` uses `matplotlib.use("Agg")` for headless rendering. Tests are heavily parametrized across all built-in templates. New layout or data-handling changes should be validated against the systematic tests.
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
name: Run tests and linting
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
pull_request:
|
|
5
|
+
branches: [ main ]
|
|
6
|
+
paths:
|
|
7
|
+
- '**.py'
|
|
8
|
+
- 'pyproject.toml'
|
|
9
|
+
- 'pixi.lock'
|
|
10
|
+
- '.github/workflows/ci.yml'
|
|
11
|
+
push:
|
|
12
|
+
branches: [ main ]
|
|
13
|
+
paths:
|
|
14
|
+
- '**.py'
|
|
15
|
+
- 'pyproject.toml'
|
|
16
|
+
- 'pixi.lock'
|
|
17
|
+
- '.github/workflows/ci.yml'
|
|
18
|
+
|
|
19
|
+
jobs:
|
|
20
|
+
setup:
|
|
21
|
+
runs-on: ubuntu-latest
|
|
22
|
+
|
|
23
|
+
steps:
|
|
24
|
+
- name: Checkout code
|
|
25
|
+
uses: actions/checkout@v4
|
|
26
|
+
|
|
27
|
+
- name: Install all pixi environments
|
|
28
|
+
uses: prefix-dev/setup-pixi@v0.9.4
|
|
29
|
+
with:
|
|
30
|
+
environments: default py38 py39 py310 py311 py312 py313 py314
|
|
31
|
+
locked: true
|
|
32
|
+
cache: true
|
|
33
|
+
cache-write: ${{ github.event_name == 'push' && github.ref == 'refs/heads/main' }}
|
|
34
|
+
|
|
35
|
+
lint:
|
|
36
|
+
runs-on: ubuntu-latest
|
|
37
|
+
needs: setup
|
|
38
|
+
|
|
39
|
+
steps:
|
|
40
|
+
- name: Checkout code
|
|
41
|
+
uses: actions/checkout@v4
|
|
42
|
+
|
|
43
|
+
- name: Restore pixi environments
|
|
44
|
+
uses: prefix-dev/setup-pixi@v0.9.4
|
|
45
|
+
with:
|
|
46
|
+
environments: default
|
|
47
|
+
locked: true
|
|
48
|
+
cache: true
|
|
49
|
+
cache-write: false
|
|
50
|
+
|
|
51
|
+
- name: Lint and format check
|
|
52
|
+
run: pixi run lint
|
|
53
|
+
|
|
54
|
+
test:
|
|
55
|
+
runs-on: ubuntu-latest
|
|
56
|
+
needs: setup
|
|
57
|
+
strategy:
|
|
58
|
+
matrix:
|
|
59
|
+
include:
|
|
60
|
+
- python-version: '3.8'
|
|
61
|
+
environment: py38
|
|
62
|
+
- python-version: '3.9'
|
|
63
|
+
environment: py39
|
|
64
|
+
- python-version: '3.10'
|
|
65
|
+
environment: py310
|
|
66
|
+
- python-version: '3.11'
|
|
67
|
+
environment: py311
|
|
68
|
+
- python-version: '3.12'
|
|
69
|
+
environment: py312
|
|
70
|
+
- python-version: '3.13'
|
|
71
|
+
environment: py313
|
|
72
|
+
- python-version: '3.14'
|
|
73
|
+
environment: py314
|
|
74
|
+
fail-fast: false
|
|
75
|
+
|
|
76
|
+
steps:
|
|
77
|
+
- name: Checkout code
|
|
78
|
+
uses: actions/checkout@v4
|
|
79
|
+
|
|
80
|
+
- name: Restore pixi environment
|
|
81
|
+
uses: prefix-dev/setup-pixi@v0.9.4
|
|
82
|
+
with:
|
|
83
|
+
environments: ${{ matrix.environment }}
|
|
84
|
+
locked: true
|
|
85
|
+
cache: true
|
|
86
|
+
cache-write: false
|
|
87
|
+
|
|
88
|
+
- name: Cache PTB-XL data
|
|
89
|
+
uses: actions/cache@v4
|
|
90
|
+
with:
|
|
91
|
+
path: tests/.ptbxl-cache
|
|
92
|
+
key: ptbxl-${{ hashFiles('tests/ptbxl_helper.py') }}
|
|
93
|
+
|
|
94
|
+
- name: Run tests on Python ${{ matrix.python-version }}
|
|
95
|
+
run: pixi run -e ${{ matrix.environment }} test
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
name: Release pmecg
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
tags:
|
|
6
|
+
- "v*"
|
|
7
|
+
|
|
8
|
+
jobs:
|
|
9
|
+
build:
|
|
10
|
+
name: Build wheel
|
|
11
|
+
runs-on: ubuntu-latest
|
|
12
|
+
|
|
13
|
+
steps:
|
|
14
|
+
- uses: actions/checkout@v4
|
|
15
|
+
with:
|
|
16
|
+
persist-credentials: false
|
|
17
|
+
|
|
18
|
+
- name: Set up pixi
|
|
19
|
+
uses: prefix-dev/setup-pixi@v0.9.4
|
|
20
|
+
with:
|
|
21
|
+
environments: default
|
|
22
|
+
locked: false
|
|
23
|
+
cache: true
|
|
24
|
+
|
|
25
|
+
- name: Build wheel and source tarball
|
|
26
|
+
run: pixi run build-dist
|
|
27
|
+
|
|
28
|
+
- name: Store distribution packages
|
|
29
|
+
uses: actions/upload-artifact@v4
|
|
30
|
+
with:
|
|
31
|
+
name: python-package-distributions
|
|
32
|
+
path: dist/
|
|
33
|
+
|
|
34
|
+
github-release:
|
|
35
|
+
name: Publish to GitHub Release
|
|
36
|
+
needs: build
|
|
37
|
+
runs-on: ubuntu-latest
|
|
38
|
+
|
|
39
|
+
permissions:
|
|
40
|
+
contents: write
|
|
41
|
+
|
|
42
|
+
steps:
|
|
43
|
+
- name: Download distributions
|
|
44
|
+
uses: actions/download-artifact@v4
|
|
45
|
+
with:
|
|
46
|
+
name: python-package-distributions
|
|
47
|
+
path: dist/
|
|
48
|
+
|
|
49
|
+
- name: Create GitHub Release and upload assets
|
|
50
|
+
env:
|
|
51
|
+
GITHUB_TOKEN: ${{ github.token }}
|
|
52
|
+
run: |
|
|
53
|
+
TAG="${GITHUB_REF#refs/tags/}"
|
|
54
|
+
gh release create "$TAG" dist/** \
|
|
55
|
+
--repo "$GITHUB_REPOSITORY" \
|
|
56
|
+
--title "pmecg $TAG" \
|
|
57
|
+
--generate-notes
|
|
58
|
+
|
|
59
|
+
publish-pypi:
|
|
60
|
+
name: Publish to PyPI
|
|
61
|
+
needs: github-release
|
|
62
|
+
runs-on: ubuntu-latest
|
|
63
|
+
|
|
64
|
+
environment:
|
|
65
|
+
name: pypi
|
|
66
|
+
url: https://pypi.org/p/pmecg
|
|
67
|
+
|
|
68
|
+
permissions:
|
|
69
|
+
id-token: write
|
|
70
|
+
|
|
71
|
+
steps:
|
|
72
|
+
- name: Download distributions
|
|
73
|
+
uses: actions/download-artifact@v4
|
|
74
|
+
with:
|
|
75
|
+
name: python-package-distributions
|
|
76
|
+
path: dist/
|
|
77
|
+
|
|
78
|
+
- name: Publish to PyPI
|
|
79
|
+
uses: pypa/gh-action-pypi-publish@release/v1
|
|
80
|
+
|
|
81
|
+
trigger-rtd:
|
|
82
|
+
name: Trigger Read the Docs build
|
|
83
|
+
needs: [github-release, publish-pypi]
|
|
84
|
+
runs-on: ubuntu-latest
|
|
85
|
+
|
|
86
|
+
steps:
|
|
87
|
+
- name: Trigger RTD webhook
|
|
88
|
+
run: |
|
|
89
|
+
PAYLOAD="{\"ref\":\"${GITHUB_REF}\"}"
|
|
90
|
+
SIG=$(echo -n "$PAYLOAD" | openssl dgst -sha256 -hmac "${{ secrets.RTD_SECRET }}" | awk '{print $2}')
|
|
91
|
+
curl -X POST \
|
|
92
|
+
-H "X-Hub-Signature-256: sha256=$SIG" \
|
|
93
|
+
-H "Content-Type: application/json" \
|
|
94
|
+
-d "$PAYLOAD" \
|
|
95
|
+
https://app.readthedocs.org/api/v2/webhook/pmecg/322018/
|
pmecg-0.2.3/.gitignore
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
# Python-generated files
|
|
2
|
+
__pycache__/
|
|
3
|
+
*.py[oc]
|
|
4
|
+
build/
|
|
5
|
+
dist/
|
|
6
|
+
wheels/
|
|
7
|
+
*.egg-info
|
|
8
|
+
|
|
9
|
+
# Virtual environments
|
|
10
|
+
.venv
|
|
11
|
+
|
|
12
|
+
# Gemini repo
|
|
13
|
+
.gemini.md
|
|
14
|
+
|
|
15
|
+
# CLAUDE
|
|
16
|
+
CLAUDE.md
|
|
17
|
+
|
|
18
|
+
# uv
|
|
19
|
+
uv.lock
|
|
20
|
+
|
|
21
|
+
# IDE
|
|
22
|
+
.vscode/
|
|
23
|
+
.idea/
|
|
24
|
+
*.swp
|
|
25
|
+
*.swo
|
|
26
|
+
|
|
27
|
+
# OS
|
|
28
|
+
.DS_Store
|
|
29
|
+
Thumbs.db
|
|
30
|
+
|
|
31
|
+
# Test / coverage
|
|
32
|
+
.pytest_cache/
|
|
33
|
+
.coverage
|
|
34
|
+
htmlcov/
|
|
35
|
+
simple_plot.py
|
|
36
|
+
example_ecg.mat
|
|
37
|
+
tests/.ptbxl-cache/
|
|
38
|
+
|
|
39
|
+
# Docs builds
|
|
40
|
+
_build/
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
3.12
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
# Read the Docs configuration file
|
|
2
|
+
# See https://docs.readthedocs.io/en/stable/config-file/v2.html for details
|
|
3
|
+
|
|
4
|
+
# Required
|
|
5
|
+
version: 2
|
|
6
|
+
|
|
7
|
+
build:
|
|
8
|
+
os: ubuntu-24.04
|
|
9
|
+
tools:
|
|
10
|
+
python: "3.13"
|
|
11
|
+
commands:
|
|
12
|
+
- curl -fsSL https://pixi.sh/install.sh | bash
|
|
13
|
+
- $HOME/.pixi/bin/pixi run build-docs
|
|
14
|
+
- mkdir -p $READTHEDOCS_OUTPUT/html && cp -r docs/_build/html/. $READTHEDOCS_OUTPUT/html/
|