Files changed (5) hide show
  1. .gitignore +15 -0
  2. ecg_scaler.pkl +3 -0
  3. echoingecg.pt +3 -0
  4. preprocessor.py +86 -0
  5. requirements.txt +182 -0
.gitignore ADDED
@@ -0,0 +1,15 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ .log/
2
+ .vscode/
3
+ __pycache__/
4
+ assets_local/
5
+ */__pycache__/
6
+ .venv/
7
+ _t*.py
8
+ _*.yaml
9
+ _develop_*.py
10
+ outputs/
11
+ jupyter/*
12
+ *.csv
13
+ third_party/*
14
+ dev_*.py
15
+ .venv
ecg_scaler.pkl ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:5c5e04979442ad03982b01b68c922fa7501bdbbacc6a4ad2b6ce3f85143fde5a
3
+ size 903
echoingecg.pt ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:31c9e1077dd2bea9a2372787beea9971269cf50ba7367f026e4d76e40d33db32
3
+ size 943726418
preprocessor.py ADDED
@@ -0,0 +1,86 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import torch
2
+ from math import gcd
3
+ from typing import Optional, Union
4
+ import joblib
5
+
6
+ import numpy as np
7
+ from scipy import signal
8
+
9
+ def load_scaler_joblib(path: str) -> tuple[torch.Tensor, torch.Tensor]:
10
+ """
11
+ Load ecg_scaler.pkl and return center and scale as torch tensors.
12
+ Args:
13
+ path: Path to the joblib file.
14
+ Returns:
15
+ center: torch.Tensor
16
+ scale: torch.Tensor
17
+ """
18
+ sc = joblib.load(path)
19
+ center = torch.from_numpy(sc.mean_.astype(np.float32))
20
+ scale = torch.from_numpy(sc.scale_.astype(np.float32)).clamp_min(1e-8)
21
+ return center, scale
22
+
23
+ class ECGTransform:
24
+ """
25
+ Unified ECG preprocessing: downsampling and scaling.
26
+ Usage:
27
+ transform = ECGTransform(center, scale, src_fs=512, target_fs=100)
28
+ ecg_out = transform(ecg_in)
29
+ """
30
+ def __init__(
31
+ self,
32
+ center: Union[np.ndarray, torch.Tensor],
33
+ scale: Union[np.ndarray, torch.Tensor],
34
+ src_fs: int = 100, #we assume the input ECG is already at 100Hz
35
+ target_fs: int = 100,
36
+ band: Optional[tuple[float, float]] = (0.5, 40.0),
37
+ bp_order: int = 4,
38
+ axis: int = -1,
39
+ ) -> None:
40
+ self.center = torch.as_tensor(center, dtype=torch.float32)
41
+ self.scale = torch.as_tensor(scale, dtype=torch.float32).clamp_min(1e-8)
42
+ self.src_fs = src_fs
43
+ self.target_fs = target_fs
44
+ self.band = band
45
+ self.bp_order = bp_order
46
+ self.axis = axis
47
+
48
+ def downsample(self, x: np.ndarray) -> np.ndarray:
49
+ x = np.asarray(x)
50
+ if self.band is not None:
51
+ lowcut, highcut = self.band
52
+ max_high = 0.45 * self.target_fs
53
+ highcut = min(highcut, max_high)
54
+ nyq = self.src_fs / 2.0
55
+ if lowcut <= 0:
56
+ wn = highcut / nyq
57
+ sos = signal.butter(self.bp_order, wn, btype="low", output="sos")
58
+ else:
59
+ wn = (lowcut / nyq, highcut / nyq)
60
+ sos = signal.butter(self.bp_order, wn, btype="band", output="sos")
61
+ x = signal.sosfiltfilt(sos, x, axis=self.axis)
62
+ g = gcd(self.src_fs, self.target_fs)
63
+ up = self.target_fs // g
64
+ down = self.src_fs // g
65
+ y = signal.resample_poly(x, up, down, axis=self.axis, window=("kaiser", 5.0), padtype="median")
66
+ return y
67
+
68
+ def scale(self, ecg: torch.Tensor) -> torch.Tensor:
69
+ ecg = ecg.to(torch.float32)
70
+ ecg = (ecg - self.center[:, None]) / self.scale[:, None]
71
+ return ecg
72
+
73
+ def __call__(self, x: np.ndarray) -> torch.Tensor:
74
+ """
75
+ Downsample and scale ECG data.
76
+ Args:
77
+ x: np.ndarray, shape (leads, time)
78
+ Returns:
79
+ torch.Tensor, shape (leads, time)
80
+ """
81
+ if self.src_fs != self.target_fs:
82
+ x = self.downsample(x)
83
+ if not isinstance(x, torch.Tensor):
84
+ x = torch.from_numpy(x)
85
+ x = self.scale(x)
86
+ return x
requirements.txt ADDED
@@ -0,0 +1,182 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ accelerate==1.10.0
2
+ aiohappyeyeballs==2.6.1
3
+ aiohttp==3.12.15
4
+ aiosignal==1.4.0
5
+ annotated-types==0.7.0
6
+ antlr4-python3-runtime==4.9.3
7
+ anyio==4.10.0
8
+ appnope==0.1.4
9
+ argon2-cffi==25.1.0
10
+ argon2-cffi-bindings==25.1.0
11
+ arrow==1.3.0
12
+ asttokens==3.0.0
13
+ async-lru==2.0.5
14
+ attrs==25.3.0
15
+ babel==2.17.0
16
+ beartype==0.21.0
17
+ beautifulsoup4==4.13.4
18
+ bleach==6.2.0
19
+ braceexpand==0.1.7
20
+ certifi==2025.8.3
21
+ cffi==1.17.1
22
+ charset-normalizer==3.4.3
23
+ click==8.2.1
24
+ comm==0.2.3
25
+ contourpy==1.3.3
26
+ cycler==0.12.1
27
+ debugpy==1.8.16
28
+ decorator==5.2.1
29
+ defusedxml==0.7.1
30
+ einops==0.8.1
31
+ executing==2.2.0
32
+ fastjsonschema==2.21.1
33
+ filelock==3.18.0
34
+ fire==0.7.0
35
+ fonttools==4.59.0
36
+ fqdn==1.5.1
37
+ frozenlist==1.7.0
38
+ fsspec==2025.7.0
39
+ ftfy==6.3.1
40
+ gitdb==4.0.12
41
+ gitpython==3.1.45
42
+ h11==0.16.0
43
+ h5py==3.14.0
44
+ hf-xet==1.1.7
45
+ httpcore==1.0.9
46
+ httpx==0.28.1
47
+ huggingface-hub==0.34.4
48
+ hydra-core==1.3.2
49
+ idna==3.10
50
+ imageio==2.37.0
51
+ iopath==0.1.10
52
+ ipykernel==6.30.1
53
+ ipython==9.4.0
54
+ ipython-pygments-lexers==1.1.1
55
+ ipywidgets==8.1.7
56
+ isoduration==20.11.0
57
+ isort==6.0.1
58
+ jedi==0.19.2
59
+ jinja2==3.1.6
60
+ joblib==1.5.1
61
+ json5==0.12.0
62
+ jsonpointer==3.0.0
63
+ jsonschema==4.25.0
64
+ jsonschema-specifications==2025.4.1
65
+ jupyter==1.1.1
66
+ jupyter-client==8.6.3
67
+ jupyter-console==6.6.3
68
+ jupyter-core==5.8.1
69
+ jupyter-events==0.12.0
70
+ jupyter-lsp==2.2.6
71
+ jupyter-server==2.16.0
72
+ jupyter-server-terminals==0.5.3
73
+ jupyterlab==4.4.5
74
+ jupyterlab-pygments==0.3.0
75
+ jupyterlab-server==2.27.3
76
+ jupyterlab-widgets==3.0.15
77
+ kernels==0.9.0
78
+ kiwisolver==1.4.8
79
+ lark==1.2.2
80
+ lazy-loader==0.4
81
+ lightning-utilities==0.15.2
82
+ markupsafe==3.0.2
83
+ matplotlib==3.10.5
84
+ matplotlib-inline==0.1.7
85
+ mistune==3.1.3
86
+ mpmath==1.3.0
87
+ multidict==6.6.3
88
+ mypy==1.17.1
89
+ mypy-extensions==1.1.0
90
+ nbclient==0.10.2
91
+ nbconvert==7.16.6
92
+ nbformat==5.10.4
93
+ nest-asyncio==1.6.0
94
+ networkx==3.5
95
+ notebook==7.4.5
96
+ notebook-shim==0.2.4
97
+ numpy==2.2.6
98
+ omegaconf==2.3.0
99
+ openai-harmony==0.0.4
100
+ opencv-python==4.12.0.88
101
+ overrides==7.7.0
102
+ packaging==25.0
103
+ pandas==2.3.1
104
+ pandocfilters==1.5.1
105
+ parso==0.8.4
106
+ pathspec==0.12.1
107
+ peft==0.17.0
108
+ pexpect==4.9.0
109
+ pillow==11.3.0
110
+ platformdirs==4.3.8
111
+ portalocker==3.2.0
112
+ prometheus-client==0.22.1
113
+ prompt-toolkit==3.0.51
114
+ propcache==0.3.2
115
+ protobuf==6.31.1
116
+ psutil==7.0.0
117
+ ptyprocess==0.7.0
118
+ pure-eval==0.2.3
119
+ pycparser==2.22
120
+ pydantic==2.11.7
121
+ pydantic-core==2.33.2
122
+ pydicom==3.0.1
123
+ pygments==2.19.2
124
+ pyparsing==3.2.3
125
+ python-dateutil==2.9.0.post0
126
+ python-json-logger==3.3.0
127
+ pytorch-lightning==2.5.2
128
+ pytz==2025.2
129
+ pyyaml==6.0.2
130
+ pyzmq==27.0.1
131
+ referencing==0.36.2
132
+ regex==2025.7.34
133
+ requests==2.32.4
134
+ rfc3339-validator==0.1.4
135
+ rfc3986-validator==0.1.1
136
+ rfc3987-syntax==1.1.0
137
+ rpds-py==0.27.0
138
+ ruff==0.12.8
139
+ safetensors==0.6.2
140
+ scikit-image==0.25.2
141
+ scikit-learn==1.7.1
142
+ scipy==1.16.1
143
+ seaborn==0.13.2
144
+ send2trash==1.8.3
145
+ sentry-sdk==2.34.1
146
+ setuptools==80.9.0
147
+ six==1.17.0
148
+ smmap==5.0.2
149
+ sniffio==1.3.1
150
+ soundfile==0.13.1
151
+ soupsieve==2.7
152
+ stack-data==0.6.3
153
+ sympy==1.14.0
154
+ termcolor==3.1.0
155
+ terminado==0.18.1
156
+ threadpoolctl==3.6.0
157
+ tifffile==2025.6.11
158
+ timm==1.0.19
159
+ tinycss2==1.4.0
160
+ tokenizers==0.21.4
161
+ torch==2.8.0
162
+ torchaudio==2.8.0
163
+ torchmetrics==1.8.1
164
+ torchvision==0.23.0
165
+ tornado==6.5.2
166
+ tqdm==4.67.1
167
+ traitlets==5.14.3
168
+ transformers==4.55.0
169
+ types-python-dateutil==2.9.0.20250809
170
+ typing-extensions==4.14.1
171
+ typing-inspection==0.4.1
172
+ tzdata==2025.2
173
+ uri-template==1.3.0
174
+ urllib3==2.5.0
175
+ wandb==0.21.1
176
+ wcwidth==0.2.13
177
+ webcolors==24.11.1
178
+ webencodings==0.5.1
179
+ websocket-client==1.8.0
180
+ wfdb==4.3.0
181
+ widgetsnbextension==4.0.14
182
+ yarl==1.20.1