Release SenuaLab patient-independent EEG IED models v1.0.0
Browse filesFive reviewed checkpoints, safetensors, aggregate vEpiSet results, provenance, hashes, and EEGAnnotationTool model pack. No private hospital data.
- MANIFEST.json +247 -0
- MODEL_LICENSE.md +37 -0
- README.md +164 -0
- eegannotationtool/senua_eegdino_medium/manifest.json +48 -0
- eegannotationtool/senua_eegdino_medium/model.pth +3 -0
- eegannotationtool/senua_eegdino_medium/model.py +73 -0
- eegannotationtool/senua_eegdino_medium/preprocessing.py +58 -0
- eegannotationtool/senua_eegpt/manifest.json +48 -0
- eegannotationtool/senua_eegpt/model.pth +3 -0
- eegannotationtool/senua_eegpt/model.py +116 -0
- eegannotationtool/senua_eegpt/preprocessing.py +58 -0
- eegannotationtool/senua_eegpt_linear_probe/manifest.json +48 -0
- eegannotationtool/senua_eegpt_linear_probe/model.pth +3 -0
- eegannotationtool/senua_eegpt_linear_probe/model.py +77 -0
- eegannotationtool/senua_eegpt_linear_probe/preprocessing.py +58 -0
- eegannotationtool/senua_iednet_lite/manifest.json +48 -0
- eegannotationtool/senua_iednet_lite/model.pth +3 -0
- eegannotationtool/senua_iednet_lite/model.py +119 -0
- eegannotationtool/senua_iednet_lite/preprocessing.py +58 -0
- eegannotationtool/senua_resnet_attention/manifest.json +48 -0
- eegannotationtool/senua_resnet_attention/model.pth +3 -0
- eegannotationtool/senua_resnet_attention/model.py +677 -0
- eegannotationtool/senua_resnet_attention/preprocessing.py +58 -0
- ensembles/ensemble.json +25 -0
- ensembles/ensemble_4model.json +53 -0
- ensembles/ensemble_eegdino.json +51 -0
- ensembles/ensemble_eegpt.json +50 -0
- models/eegdino_medium/config.json +130 -0
- models/eegdino_medium/model.safetensors +3 -0
- models/eegpt/config.json +122 -0
- models/eegpt/model.safetensors +3 -0
- models/eegpt_linear_probe/config.json +122 -0
- models/eegpt_linear_probe/model.safetensors +3 -0
- models/iednet_lite/config.json +89 -0
- models/iednet_lite/model.safetensors +3 -0
- models/resnet_attention/config.json +89 -0
- models/resnet_attention/model.safetensors +3 -0
- pytorch/eegdino_medium/config.json +130 -0
- pytorch/eegdino_medium/model.pth +3 -0
- pytorch/eegpt/config.json +122 -0
- pytorch/eegpt/model.pth +3 -0
- pytorch/eegpt_linear_probe/config.json +122 -0
- pytorch/eegpt_linear_probe/model.pth +3 -0
- pytorch/iednet_lite/config.json +89 -0
- pytorch/iednet_lite/model.pth +3 -0
- pytorch/resnet_attention/config.json +89 -0
- pytorch/resnet_attention/model.pth +3 -0
- results/benchmark_results.csv +8 -0
- results/model_comparison.png +0 -0
MANIFEST.json
ADDED
|
@@ -0,0 +1,247 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"files": [
|
| 3 |
+
{
|
| 4 |
+
"bytes": 1069,
|
| 5 |
+
"path": "eegannotationtool/senua_eegdino_medium/manifest.json",
|
| 6 |
+
"sha256": "c73c5997c96a0f7b46ad885348f443b82a8a078b59a6731d8664750563660d47"
|
| 7 |
+
},
|
| 8 |
+
{
|
| 9 |
+
"bytes": 137840831,
|
| 10 |
+
"path": "eegannotationtool/senua_eegdino_medium/model.pth",
|
| 11 |
+
"sha256": "e80ee1f66edcac1059b6e68c7458d8aa171167fbc9d50495daaafdc20e358491"
|
| 12 |
+
},
|
| 13 |
+
{
|
| 14 |
+
"bytes": 2712,
|
| 15 |
+
"path": "eegannotationtool/senua_eegdino_medium/model.py",
|
| 16 |
+
"sha256": "1bda5a0ee0fc8e58835eec379294659a02f804cf37a3227d056807553f3a497a"
|
| 17 |
+
},
|
| 18 |
+
{
|
| 19 |
+
"bytes": 2040,
|
| 20 |
+
"path": "eegannotationtool/senua_eegdino_medium/preprocessing.py",
|
| 21 |
+
"sha256": "1b39acec0d87439763009017a60614dda25a0605241c2563c8d1cf2c0c45ca44"
|
| 22 |
+
},
|
| 23 |
+
{
|
| 24 |
+
"bytes": 1062,
|
| 25 |
+
"path": "eegannotationtool/senua_eegpt/manifest.json",
|
| 26 |
+
"sha256": "aaa71a18f34e62ab9a0fc7dac718b23b0508d730bc93ce61921a7b1d68cc90a0"
|
| 27 |
+
},
|
| 28 |
+
{
|
| 29 |
+
"bytes": 102394167,
|
| 30 |
+
"path": "eegannotationtool/senua_eegpt/model.pth",
|
| 31 |
+
"sha256": "9a0a55ed656049b4109d0e923ee0d461151a8a793995a24438717efecd2f24d5"
|
| 32 |
+
},
|
| 33 |
+
{
|
| 34 |
+
"bytes": 4422,
|
| 35 |
+
"path": "eegannotationtool/senua_eegpt/model.py",
|
| 36 |
+
"sha256": "ed238c01a7c275f4ccc5315af11a2fc1694b497843f120bfa5a40d9f560c7a7f"
|
| 37 |
+
},
|
| 38 |
+
{
|
| 39 |
+
"bytes": 2040,
|
| 40 |
+
"path": "eegannotationtool/senua_eegpt/preprocessing.py",
|
| 41 |
+
"sha256": "1b39acec0d87439763009017a60614dda25a0605241c2563c8d1cf2c0c45ca44"
|
| 42 |
+
},
|
| 43 |
+
{
|
| 44 |
+
"bytes": 1073,
|
| 45 |
+
"path": "eegannotationtool/senua_eegpt_linear_probe/manifest.json",
|
| 46 |
+
"sha256": "8c7474f782ef8089fe03eb307b61d9c324c79e4ec979245bd1fab898a6617dff"
|
| 47 |
+
},
|
| 48 |
+
{
|
| 49 |
+
"bytes": 101312967,
|
| 50 |
+
"path": "eegannotationtool/senua_eegpt_linear_probe/model.pth",
|
| 51 |
+
"sha256": "a51fd32505d51d80677ed6504b94d0b8a6718cb1de21a07cd3253ed1e1a3405a"
|
| 52 |
+
},
|
| 53 |
+
{
|
| 54 |
+
"bytes": 2681,
|
| 55 |
+
"path": "eegannotationtool/senua_eegpt_linear_probe/model.py",
|
| 56 |
+
"sha256": "e788f854165481d594b5727a3c080e76a52286113cdcab0057a305ff451dc5f9"
|
| 57 |
+
},
|
| 58 |
+
{
|
| 59 |
+
"bytes": 2040,
|
| 60 |
+
"path": "eegannotationtool/senua_eegpt_linear_probe/preprocessing.py",
|
| 61 |
+
"sha256": "1b39acec0d87439763009017a60614dda25a0605241c2563c8d1cf2c0c45ca44"
|
| 62 |
+
},
|
| 63 |
+
{
|
| 64 |
+
"bytes": 1074,
|
| 65 |
+
"path": "eegannotationtool/senua_iednet_lite/manifest.json",
|
| 66 |
+
"sha256": "9dabb0feb431ff763b804c0435363ad443351177e58355c4399e7572f10b7cfe"
|
| 67 |
+
},
|
| 68 |
+
{
|
| 69 |
+
"bytes": 1041083,
|
| 70 |
+
"path": "eegannotationtool/senua_iednet_lite/model.pth",
|
| 71 |
+
"sha256": "383fe1de4aa8cf01b5f1c0a6734d92214ec0e7f96a6cd18a50fa52c52ccb210c"
|
| 72 |
+
},
|
| 73 |
+
{
|
| 74 |
+
"bytes": 4388,
|
| 75 |
+
"path": "eegannotationtool/senua_iednet_lite/model.py",
|
| 76 |
+
"sha256": "1a221ee13a67fb51cf26915e049a2eccfc4250d1df1adb150e312d6c677feab0"
|
| 77 |
+
},
|
| 78 |
+
{
|
| 79 |
+
"bytes": 2040,
|
| 80 |
+
"path": "eegannotationtool/senua_iednet_lite/preprocessing.py",
|
| 81 |
+
"sha256": "1b39acec0d87439763009017a60614dda25a0605241c2563c8d1cf2c0c45ca44"
|
| 82 |
+
},
|
| 83 |
+
{
|
| 84 |
+
"bytes": 1079,
|
| 85 |
+
"path": "eegannotationtool/senua_resnet_attention/manifest.json",
|
| 86 |
+
"sha256": "a1ef046db3b2edcd752d1b93e0a540508b620f1d1a4b6f76cb20d2a64cac1784"
|
| 87 |
+
},
|
| 88 |
+
{
|
| 89 |
+
"bytes": 20037039,
|
| 90 |
+
"path": "eegannotationtool/senua_resnet_attention/model.pth",
|
| 91 |
+
"sha256": "df088c4fd087b453ef3aca76f54a975a69f015324f8d862d2d4ba5316cb91e1a"
|
| 92 |
+
},
|
| 93 |
+
{
|
| 94 |
+
"bytes": 24358,
|
| 95 |
+
"path": "eegannotationtool/senua_resnet_attention/model.py",
|
| 96 |
+
"sha256": "f3be755ac6c0e3b95e1f48db9fe7af0df88aeda5f36d7d7d793fdf51c6355290"
|
| 97 |
+
},
|
| 98 |
+
{
|
| 99 |
+
"bytes": 2040,
|
| 100 |
+
"path": "eegannotationtool/senua_resnet_attention/preprocessing.py",
|
| 101 |
+
"sha256": "1b39acec0d87439763009017a60614dda25a0605241c2563c8d1cf2c0c45ca44"
|
| 102 |
+
},
|
| 103 |
+
{
|
| 104 |
+
"bytes": 627,
|
| 105 |
+
"path": "ensembles/ensemble.json",
|
| 106 |
+
"sha256": "793881c7515f44ae70e77a1ba599f606c15b0fbd551318fbf6e4ea5b18a7ebf2"
|
| 107 |
+
},
|
| 108 |
+
{
|
| 109 |
+
"bytes": 1390,
|
| 110 |
+
"path": "ensembles/ensemble_4model.json",
|
| 111 |
+
"sha256": "e44924fde4bc43d8cd9305f2a8a0e1727c0164a31d8e1ca494c5af55b90eba74"
|
| 112 |
+
},
|
| 113 |
+
{
|
| 114 |
+
"bytes": 1425,
|
| 115 |
+
"path": "ensembles/ensemble_eegdino.json",
|
| 116 |
+
"sha256": "0d2f9c2d9ac605423d47a9739ea9466cfd19544553ab233db34d0db09cc57172"
|
| 117 |
+
},
|
| 118 |
+
{
|
| 119 |
+
"bytes": 1326,
|
| 120 |
+
"path": "ensembles/ensemble_eegpt.json",
|
| 121 |
+
"sha256": "813ac3a4bcfa50c6c4ae81a79ac8faec9ae2e8dc5bfdab454a98572fd5698dd9"
|
| 122 |
+
},
|
| 123 |
+
{
|
| 124 |
+
"bytes": 1646,
|
| 125 |
+
"path": "MODEL_LICENSE.md",
|
| 126 |
+
"sha256": "747884c797383451a74c7444148d0fd1182245960aca48582df35f758a8c3831"
|
| 127 |
+
},
|
| 128 |
+
{
|
| 129 |
+
"bytes": 3165,
|
| 130 |
+
"path": "models/eegdino_medium/config.json",
|
| 131 |
+
"sha256": "e53ddd10b026440bed401b4b1c3468aaf9b43e4e5f9074655cf56389848a66f2"
|
| 132 |
+
},
|
| 133 |
+
{
|
| 134 |
+
"bytes": 137804848,
|
| 135 |
+
"path": "models/eegdino_medium/model.safetensors",
|
| 136 |
+
"sha256": "72320fedeed09b66508afaa79ce0ca114273673f6ce36adccedce87c8ce0ea1d"
|
| 137 |
+
},
|
| 138 |
+
{
|
| 139 |
+
"bytes": 2811,
|
| 140 |
+
"path": "models/eegpt/config.json",
|
| 141 |
+
"sha256": "fd8db52c30918c858c1924edfeb513388685e00cc662d920b027d8900f04e1d7"
|
| 142 |
+
},
|
| 143 |
+
{
|
| 144 |
+
"bytes": 102373540,
|
| 145 |
+
"path": "models/eegpt/model.safetensors",
|
| 146 |
+
"sha256": "e91757b80c98dd7325ea1068bff9dfe72bac577195f5b25b2570ce4d9f357226"
|
| 147 |
+
},
|
| 148 |
+
{
|
| 149 |
+
"bytes": 2824,
|
| 150 |
+
"path": "models/eegpt_linear_probe/config.json",
|
| 151 |
+
"sha256": "267732d264b787020c3e2b4285e56798bf644e8a26876645e31cff0ad273b2e7"
|
| 152 |
+
},
|
| 153 |
+
{
|
| 154 |
+
"bytes": 101295352,
|
| 155 |
+
"path": "models/eegpt_linear_probe/model.safetensors",
|
| 156 |
+
"sha256": "24ec011a90388079df98322244b96e076eeebf6b0671fe987a7d0d5cf66f9579"
|
| 157 |
+
},
|
| 158 |
+
{
|
| 159 |
+
"bytes": 2019,
|
| 160 |
+
"path": "models/iednet_lite/config.json",
|
| 161 |
+
"sha256": "9fc65e9684a39fe22ea8660bd56526319965704d5968c66309be350d6b743b38"
|
| 162 |
+
},
|
| 163 |
+
{
|
| 164 |
+
"bytes": 1014056,
|
| 165 |
+
"path": "models/iednet_lite/model.safetensors",
|
| 166 |
+
"sha256": "7c5dc3c8e40050b69a07a7cf6a1ce7bbd2b12347f1b50d7feaf1fdfdd108b082"
|
| 167 |
+
},
|
| 168 |
+
{
|
| 169 |
+
"bytes": 2030,
|
| 170 |
+
"path": "models/resnet_attention/config.json",
|
| 171 |
+
"sha256": "d85aa13650584b0c0489dc2aaec89b4007d2aa61cf25ff55ad73cca621fac651"
|
| 172 |
+
},
|
| 173 |
+
{
|
| 174 |
+
"bytes": 20011088,
|
| 175 |
+
"path": "models/resnet_attention/model.safetensors",
|
| 176 |
+
"sha256": "971fa0d2b38f0cc76ca479931872ae957123aeb51263de59b11098be1531b1e1"
|
| 177 |
+
},
|
| 178 |
+
{
|
| 179 |
+
"bytes": 3165,
|
| 180 |
+
"path": "pytorch/eegdino_medium/config.json",
|
| 181 |
+
"sha256": "e53ddd10b026440bed401b4b1c3468aaf9b43e4e5f9074655cf56389848a66f2"
|
| 182 |
+
},
|
| 183 |
+
{
|
| 184 |
+
"bytes": 137840831,
|
| 185 |
+
"path": "pytorch/eegdino_medium/model.pth",
|
| 186 |
+
"sha256": "e80ee1f66edcac1059b6e68c7458d8aa171167fbc9d50495daaafdc20e358491"
|
| 187 |
+
},
|
| 188 |
+
{
|
| 189 |
+
"bytes": 2811,
|
| 190 |
+
"path": "pytorch/eegpt/config.json",
|
| 191 |
+
"sha256": "fd8db52c30918c858c1924edfeb513388685e00cc662d920b027d8900f04e1d7"
|
| 192 |
+
},
|
| 193 |
+
{
|
| 194 |
+
"bytes": 102394167,
|
| 195 |
+
"path": "pytorch/eegpt/model.pth",
|
| 196 |
+
"sha256": "9a0a55ed656049b4109d0e923ee0d461151a8a793995a24438717efecd2f24d5"
|
| 197 |
+
},
|
| 198 |
+
{
|
| 199 |
+
"bytes": 2824,
|
| 200 |
+
"path": "pytorch/eegpt_linear_probe/config.json",
|
| 201 |
+
"sha256": "267732d264b787020c3e2b4285e56798bf644e8a26876645e31cff0ad273b2e7"
|
| 202 |
+
},
|
| 203 |
+
{
|
| 204 |
+
"bytes": 101312967,
|
| 205 |
+
"path": "pytorch/eegpt_linear_probe/model.pth",
|
| 206 |
+
"sha256": "a51fd32505d51d80677ed6504b94d0b8a6718cb1de21a07cd3253ed1e1a3405a"
|
| 207 |
+
},
|
| 208 |
+
{
|
| 209 |
+
"bytes": 2019,
|
| 210 |
+
"path": "pytorch/iednet_lite/config.json",
|
| 211 |
+
"sha256": "9fc65e9684a39fe22ea8660bd56526319965704d5968c66309be350d6b743b38"
|
| 212 |
+
},
|
| 213 |
+
{
|
| 214 |
+
"bytes": 1041083,
|
| 215 |
+
"path": "pytorch/iednet_lite/model.pth",
|
| 216 |
+
"sha256": "383fe1de4aa8cf01b5f1c0a6734d92214ec0e7f96a6cd18a50fa52c52ccb210c"
|
| 217 |
+
},
|
| 218 |
+
{
|
| 219 |
+
"bytes": 2030,
|
| 220 |
+
"path": "pytorch/resnet_attention/config.json",
|
| 221 |
+
"sha256": "d85aa13650584b0c0489dc2aaec89b4007d2aa61cf25ff55ad73cca621fac651"
|
| 222 |
+
},
|
| 223 |
+
{
|
| 224 |
+
"bytes": 20037039,
|
| 225 |
+
"path": "pytorch/resnet_attention/model.pth",
|
| 226 |
+
"sha256": "df088c4fd087b453ef3aca76f54a975a69f015324f8d862d2d4ba5316cb91e1a"
|
| 227 |
+
},
|
| 228 |
+
{
|
| 229 |
+
"bytes": 5952,
|
| 230 |
+
"path": "README.md",
|
| 231 |
+
"sha256": "f4b252478ceba9980edc00f5edcd7bb8550bb0e758a9a9f7274a4b2445b5077b"
|
| 232 |
+
},
|
| 233 |
+
{
|
| 234 |
+
"bytes": 1839,
|
| 235 |
+
"path": "results/benchmark_results.csv",
|
| 236 |
+
"sha256": "4c09b55325f8571ce374aa274a6ed395a4fc69346e333ae1a884247cb5fa2f08"
|
| 237 |
+
},
|
| 238 |
+
{
|
| 239 |
+
"bytes": 76548,
|
| 240 |
+
"path": "results/model_comparison.png",
|
| 241 |
+
"sha256": "8af73e7c56b761565ec67badab0f456ed69ba0c9c88724b2e5c9d65c7d07e0d3"
|
| 242 |
+
}
|
| 243 |
+
],
|
| 244 |
+
"privacy_review": "public vEpiSet metadata and aggregate results only; no hospital data",
|
| 245 |
+
"repository": "SenuaLab/EEG-IED-Detection",
|
| 246 |
+
"schema_version": 1
|
| 247 |
+
}
|
MODEL_LICENSE.md
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Model artifact terms and provenance
|
| 2 |
+
|
| 3 |
+
There is no single license covering every artifact in this multi-model
|
| 4 |
+
repository. The SenuaLab source repository is MIT licensed. vEpiSet is CC BY
|
| 5 |
+
4.0. Upstream foundation-model code and checkpoints retain their respective
|
| 6 |
+
terms.
|
| 7 |
+
|
| 8 |
+
## From-scratch checkpoints
|
| 9 |
+
|
| 10 |
+
`iednet_lite` and `resnet_attention` are SenuaLab architectures/checkpoints
|
| 11 |
+
trained on vEpiSet. SenuaLab makes its contribution available under CC BY 4.0,
|
| 12 |
+
with required attribution to SenuaLab and the vEpiSet publication.
|
| 13 |
+
|
| 14 |
+
## EEGPT checkpoints
|
| 15 |
+
|
| 16 |
+
`eegpt` and `eegpt_linear_probe` contain encoder parameters derived from
|
| 17 |
+
`braindecode/eegpt-pretrained`. The original EEGPT code is Apache-2.0 and the
|
| 18 |
+
curated Braindecode model card identifies BSD-3-Clause. SenuaLab's downstream
|
| 19 |
+
head/configuration is made available under CC BY 4.0. Upstream notices and
|
| 20 |
+
attribution must be retained.
|
| 21 |
+
|
| 22 |
+
## EEG-DINO Medium checkpoint
|
| 23 |
+
|
| 24 |
+
`eegdino_medium` contains encoder parameters derived from
|
| 25 |
+
`braindecode/eegdino-medium-pretrained`. The curated model code is identified as
|
| 26 |
+
BSD-3-Clause. SenuaLab's downstream task head and fine-tuning contribution are
|
| 27 |
+
made available under CC BY 4.0. The upstream model card warns that pretraining
|
| 28 |
+
corpus terms may add restrictions; users must independently verify eligibility,
|
| 29 |
+
especially for commercial deployment.
|
| 30 |
+
|
| 31 |
+
## No clinical warranty
|
| 32 |
+
|
| 33 |
+
Artifacts are provided as-is for research and engineering evaluation. They are
|
| 34 |
+
not medical devices, have no warranty of clinical fitness, and require expert
|
| 35 |
+
review. These terms do not override applicable upstream licenses, dataset
|
| 36 |
+
terms, law, institutional policy, or regulatory requirements.
|
| 37 |
+
|
README.md
ADDED
|
@@ -0,0 +1,164 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
---
|
| 2 |
+
license: other
|
| 3 |
+
library_name: pytorch
|
| 4 |
+
pipeline_tag: audio-classification
|
| 5 |
+
tags:
|
| 6 |
+
- eeg
|
| 7 |
+
- epilepsy
|
| 8 |
+
- interictal-epileptiform-discharge
|
| 9 |
+
- neuroscience
|
| 10 |
+
- eegpt
|
| 11 |
+
- eeg-dino
|
| 12 |
+
- braindecode
|
| 13 |
+
datasets:
|
| 14 |
+
- vEpiSet
|
| 15 |
+
base_model:
|
| 16 |
+
- braindecode/eegpt-pretrained
|
| 17 |
+
- braindecode/eegdino-medium-pretrained
|
| 18 |
+
---
|
| 19 |
+
|
| 20 |
+
# SenuaLab EEG IED Detection
|
| 21 |
+
|
| 22 |
+
Five patient-independent binary IED checkpoints, aggregate benchmark artifacts,
|
| 23 |
+
and an app-ready model pack for
|
| 24 |
+
[SenuaLab EEG Annotation Tool](https://github.com/SenuaLab/EEGAnnotationTool).
|
| 25 |
+
Training and evaluation use only the public
|
| 26 |
+
[vEpiSet](https://doi.org/10.6084/m9.figshare.28069568) dataset.
|
| 27 |
+
|
| 28 |
+
> **Research use only.** These models are not medical devices and must not be
|
| 29 |
+
> used as the sole basis for diagnosis or treatment. Every candidate requires
|
| 30 |
+
> review by a qualified EEG professional against the original recording.
|
| 31 |
+
|
| 32 |
+
> **Privacy.** No private hospital recording, patient identifier, physician
|
| 33 |
+
> annotation, local path, or hospital-derived patient result is included. The
|
| 34 |
+
> model parameters, public configuration metadata, and aggregate vEpiSet
|
| 35 |
+
> results were reviewed before upload.
|
| 36 |
+
|
| 37 |
+
Source, methodology, and reproducibility documentation:
|
| 38 |
+
[github.com/SenuaLab/EEG-IED-Detection](https://github.com/SenuaLab/EEG-IED-Detection)
|
| 39 |
+
|
| 40 |
+
## Models
|
| 41 |
+
|
| 42 |
+
| Folder | Model | Params | Rate | Window | Test AUROC | Test AUPRC | Test F1 |
|
| 43 |
+
|---|---|---:|---:|---:|---:|---:|---:|
|
| 44 |
+
| `iednet_lite` | IEDNet-Lite | 0.25M | 250 Hz | 4 s | 0.8793 | 0.6848 | 0.6372 |
|
| 45 |
+
| `resnet_attention` | ResNet-Attention | 4.99M | 250 Hz | 4 s | 0.8998 | 0.7018 | 0.6667 |
|
| 46 |
+
| `eegpt` | EEGPT + temporal head | 25.59M | 250 Hz | 4 s | 0.8641 | 0.6532 | 0.6139 |
|
| 47 |
+
| `eegpt_linear_probe` | EEGPT linear probe | 25.32M | 250 Hz | 4 s | 0.7519 | 0.3722 | 0.3227 |
|
| 48 |
+
| `eegdino_medium` | EEG-DINO Medium | 34.45M | 200 Hz | 4 s | **0.9363** | **0.8020** | **0.7415** |
|
| 49 |
+
|
| 50 |
+
All test thresholds were selected on validation subjects and then frozen. The
|
| 51 |
+
test split contains 3,908 windows from 13 held-out vEpiSet subjects. Results are
|
| 52 |
+
single-dataset, four-second window metrics, not prospective clinical claims.
|
| 53 |
+
|
| 54 |
+
## Files
|
| 55 |
+
|
| 56 |
+
```text
|
| 57 |
+
models/<name>/
|
| 58 |
+
├── model.safetensors # tensor-only weights (preferred)
|
| 59 |
+
└── config.json # reviewed input/provenance/metric contract
|
| 60 |
+
|
| 61 |
+
pytorch/<name>/
|
| 62 |
+
├── model.pth # sanitized checkpoint for the Python runner
|
| 63 |
+
└── config.json
|
| 64 |
+
|
| 65 |
+
eegannotationtool/senua_<name>/
|
| 66 |
+
├── model.py # executable architecture adapter
|
| 67 |
+
├── preprocessing.py # released preprocessing contract
|
| 68 |
+
├── model.pth # app-compatible sanitized checkpoint
|
| 69 |
+
└── manifest.json
|
| 70 |
+
|
| 71 |
+
ensembles/ # validation-selected ensemble definitions
|
| 72 |
+
results/ # aggregate benchmark table and figure
|
| 73 |
+
MANIFEST.json # SHA-256 and byte size for every release file
|
| 74 |
+
```
|
| 75 |
+
|
| 76 |
+
PyTorch `.pth` is included because the current EEG Annotation Tool model
|
| 77 |
+
discovery contract supports `.pth/.pt/.ckpt`. Prefer `safetensors` elsewhere and
|
| 78 |
+
load executable/model files only from this official repository.
|
| 79 |
+
|
| 80 |
+
## Input contract
|
| 81 |
+
|
| 82 |
+
- 19 standard 10-20 channels in this order:
|
| 83 |
+
`Fp1, Fp2, F3, F4, C3, C4, P3, P4, O1, O2, F7, F8, T3, T4, T5, T6, Fz, Cz, Pz`.
|
| 84 |
+
- Four-second full windows.
|
| 85 |
+
- Fourth-order zero-phase 1-45 Hz Butterworth band-pass.
|
| 86 |
+
- Common-average reference across the 19 channels.
|
| 87 |
+
- Resampling to the rate listed in the table.
|
| 88 |
+
- One global z-score over the complete channel-time window, clipped to `[-8,8]`.
|
| 89 |
+
- Binary output order: `[Non-IED, IED]`.
|
| 90 |
+
|
| 91 |
+
Legacy temporal aliases T7/T8/P7/P8 map to T3/T4/T5/T6.
|
| 92 |
+
|
| 93 |
+
## Download
|
| 94 |
+
|
| 95 |
+
```bash
|
| 96 |
+
hf download SenuaLab/EEG-IED-Detection \
|
| 97 |
+
pytorch/iednet_lite/model.pth \
|
| 98 |
+
--local-dir ./weights
|
| 99 |
+
```
|
| 100 |
+
|
| 101 |
+
Or from Python:
|
| 102 |
+
|
| 103 |
+
```python
|
| 104 |
+
from huggingface_hub import hf_hub_download
|
| 105 |
+
|
| 106 |
+
path = hf_hub_download(
|
| 107 |
+
repo_id="SenuaLab/EEG-IED-Detection",
|
| 108 |
+
filename="pytorch/iednet_lite/model.pth",
|
| 109 |
+
)
|
| 110 |
+
```
|
| 111 |
+
|
| 112 |
+
The source repository runner accepts model short names and handles the download:
|
| 113 |
+
|
| 114 |
+
```bash
|
| 115 |
+
python -m eeg_ied_detector.robust_predict \
|
| 116 |
+
--data-dir /path/to/brainvision-recordings \
|
| 117 |
+
--model-paths iednet_lite resnet_attention \
|
| 118 |
+
--output-dir ./predictions \
|
| 119 |
+
--no-label-comparison
|
| 120 |
+
```
|
| 121 |
+
|
| 122 |
+
EEGPT and EEG-DINO require `braindecode[hub]==1.6.1`.
|
| 123 |
+
|
| 124 |
+
## EEG Annotation Tool
|
| 125 |
+
|
| 126 |
+
Use the installer from the source repository:
|
| 127 |
+
|
| 128 |
+
```bash
|
| 129 |
+
python scripts/install_eeg_annotation_models.py \
|
| 130 |
+
--models iednet_lite resnet_attention
|
| 131 |
+
```
|
| 132 |
+
|
| 133 |
+
IED Finder discovers the installed folders, validates channels and the declared
|
| 134 |
+
input contract, and uses the validation-selected threshold as its starting
|
| 135 |
+
point. Foundation-model adapters are included but need Braindecode in the
|
| 136 |
+
application's Python environment.
|
| 137 |
+
|
| 138 |
+
## Evaluation limitations
|
| 139 |
+
|
| 140 |
+
- One public dataset and one fixed internal held-out split.
|
| 141 |
+
- No prospective, external multi-center, or medical-device validation.
|
| 142 |
+
- Four-second window metrics do not equal continuous event-level sensitivity.
|
| 143 |
+
- Performance can shift with montage, hardware, preprocessing, age, disease
|
| 144 |
+
mix, artifacts, prevalence, and annotation policy.
|
| 145 |
+
- Threshold changes require a prospectively defined validation protocol and
|
| 146 |
+
must not use the final evaluation cohort.
|
| 147 |
+
- Automated output is a review candidate, never a diagnosis.
|
| 148 |
+
|
| 149 |
+
## Model and data provenance
|
| 150 |
+
|
| 151 |
+
- vEpiSet: Lin et al., Scientific Data 12, 229 (2025),
|
| 152 |
+
https://doi.org/10.1038/s41597-025-04523-8, CC BY 4.0.
|
| 153 |
+
- EEGPT: NeurIPS 2024, upstream code
|
| 154 |
+
[BINE022/EEGPT](https://github.com/BINE022/EEGPT), encoder checkpoint
|
| 155 |
+
[braindecode/eegpt-pretrained](https://huggingface.co/braindecode/eegpt-pretrained).
|
| 156 |
+
- EEG-DINO: MICCAI 2025,
|
| 157 |
+
[paper](https://papers.miccai.org/miccai-2025/paper/3347_paper.pdf), encoder
|
| 158 |
+
checkpoint
|
| 159 |
+
[braindecode/eegdino-medium-pretrained](https://huggingface.co/braindecode/eegdino-medium-pretrained)
|
| 160 |
+
at revision `191f73eb50d68a184b4cabb623938df914235d3c`.
|
| 161 |
+
|
| 162 |
+
See `MODEL_LICENSE.md` for the per-artifact licensing boundary and upstream
|
| 163 |
+
conditions.
|
| 164 |
+
|
eegannotationtool/senua_eegdino_medium/manifest.json
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"finder_defaults": {
|
| 3 |
+
"pad_policy": "skip",
|
| 4 |
+
"step_ms": 500.0,
|
| 5 |
+
"threshold": 0.619140625
|
| 6 |
+
},
|
| 7 |
+
"important": "Research screening model; every candidate requires expert review. Threshold selected on public vEpiSet validation subjects.",
|
| 8 |
+
"input": {
|
| 9 |
+
"channel_aliases": {
|
| 10 |
+
"P7": "T5",
|
| 11 |
+
"P8": "T6",
|
| 12 |
+
"T7": "T3",
|
| 13 |
+
"T8": "T4"
|
| 14 |
+
},
|
| 15 |
+
"channel_names": [
|
| 16 |
+
"Fp1",
|
| 17 |
+
"Fp2",
|
| 18 |
+
"F3",
|
| 19 |
+
"F4",
|
| 20 |
+
"C3",
|
| 21 |
+
"C4",
|
| 22 |
+
"P3",
|
| 23 |
+
"P4",
|
| 24 |
+
"O1",
|
| 25 |
+
"O2",
|
| 26 |
+
"F7",
|
| 27 |
+
"F8",
|
| 28 |
+
"T3",
|
| 29 |
+
"T4",
|
| 30 |
+
"T5",
|
| 31 |
+
"T6",
|
| 32 |
+
"Fz",
|
| 33 |
+
"Cz",
|
| 34 |
+
"Pz"
|
| 35 |
+
],
|
| 36 |
+
"input_samples": 800,
|
| 37 |
+
"normalization": "global four-second window z-score clipped to [-8,8]",
|
| 38 |
+
"reference": "common average",
|
| 39 |
+
"required_filters": [
|
| 40 |
+
"1-45 Hz zero-phase Butterworth"
|
| 41 |
+
],
|
| 42 |
+
"sampling_rate_hz": 200.0,
|
| 43 |
+
"window_seconds": 4.0
|
| 44 |
+
},
|
| 45 |
+
"name": "SenuaLab eegdino_medium",
|
| 46 |
+
"schema_version": 1,
|
| 47 |
+
"source": "https://huggingface.co/SenuaLab/EEG-IED-Detection"
|
| 48 |
+
}
|
eegannotationtool/senua_eegdino_medium/model.pth
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:e80ee1f66edcac1059b6e68c7458d8aa171167fbc9d50495daaafdc20e358491
|
| 3 |
+
size 137840831
|
eegannotationtool/senua_eegdino_medium/model.py
ADDED
|
@@ -0,0 +1,73 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""EEG Annotation Tool adapter for SenuaLab EEG-DINO Medium."""
|
| 2 |
+
|
| 3 |
+
from __future__ import annotations
|
| 4 |
+
|
| 5 |
+
try:
|
| 6 |
+
from braindecode.models import EEGDINO
|
| 7 |
+
except ImportError as exc:
|
| 8 |
+
raise ImportError("This model requires braindecode[hub]==1.6.1") from exc
|
| 9 |
+
|
| 10 |
+
from .preprocessing import INTERNATIONAL_10_20_CHANNELS, preprocess_batch as _preprocess
|
| 11 |
+
|
| 12 |
+
|
| 13 |
+
MODEL_CHANNELS = INTERNATIONAL_10_20_CHANNELS
|
| 14 |
+
MODEL_INPUT_SAMPLES = 800
|
| 15 |
+
MODEL_SAMPLING_RATE_HZ = 200.0
|
| 16 |
+
MODEL_WINDOW_SECONDS = 4.0
|
| 17 |
+
MODEL_ENTRY_CLASS = "SenuaEEGDINOMedium"
|
| 18 |
+
MODEL_NUM_CLASSES = 2
|
| 19 |
+
MODEL_CLASS_LABELS = ["Non-IED", "IED"]
|
| 20 |
+
MODEL_NON_IED_CLASS_INDEX = 0
|
| 21 |
+
MODEL_IED_CLASS_INDICES = [1]
|
| 22 |
+
MODEL_DESCRIPTION = "SenuaLab partially fine-tuned EEG-DINO Medium binary IED detector"
|
| 23 |
+
MODEL_BATCH_PREPROCESSOR = "preprocess_batch"
|
| 24 |
+
MODEL_REQUIRED_REFERENCE = "common average (applied by model adapter)"
|
| 25 |
+
MODEL_REQUIRED_FILTERS = ["1-45 Hz zero-phase Butterworth (applied by model adapter)"]
|
| 26 |
+
MODEL_REQUIRED_NORMALIZATION = "global four-second window z-score, clipped to [-8,8]"
|
| 27 |
+
MODEL_INPUT_UNIT = "scale-invariant after window z-score"
|
| 28 |
+
MODEL_SOURCE_SIGNAL_POLICY = "raw"
|
| 29 |
+
MODEL_REQUIRES_FULL_WINDOW = True
|
| 30 |
+
MODEL_REQUIRES_ALL_CHANNELS = True
|
| 31 |
+
MODEL_DEFAULT_THRESHOLD = 0.619140625
|
| 32 |
+
MODEL_DEFAULT_STEP_MS = 500.0
|
| 33 |
+
MODEL_DEFAULT_PAD_POLICY = "skip"
|
| 34 |
+
MODEL_DEFAULT_BATCH_SIZE = 8
|
| 35 |
+
MODEL_DEFAULT_BATCH_MEMORY_MB = 384.0
|
| 36 |
+
MODEL_VALIDATION_NOTE = "Threshold selected on vEpiSet validation subjects; requires Braindecode 1.6.1 and is not externally validated."
|
| 37 |
+
MODEL_PREPROCESSING_NOTE = "The adapter applies the released 1-45 Hz, common-average, 200 Hz resampling, and global-window z-score pipeline."
|
| 38 |
+
|
| 39 |
+
|
| 40 |
+
def preprocess_batch(batch, source_sfreq=None, channel_names=None):
|
| 41 |
+
return _preprocess(
|
| 42 |
+
batch,
|
| 43 |
+
source_sfreq=source_sfreq,
|
| 44 |
+
target_sfreq=200,
|
| 45 |
+
target_samples=800,
|
| 46 |
+
channel_names=channel_names,
|
| 47 |
+
)
|
| 48 |
+
|
| 49 |
+
|
| 50 |
+
class SenuaEEGDINOMedium(EEGDINO):
|
| 51 |
+
def __init__(self, num_channels: int = 19, num_classes: int = 2, input_length: int = 800):
|
| 52 |
+
if num_channels != 19 or input_length != 800:
|
| 53 |
+
raise ValueError("SenuaEEGDINOMedium requires 19 channels and 800 samples")
|
| 54 |
+
super().__init__(
|
| 55 |
+
n_outputs=num_classes,
|
| 56 |
+
n_chans=19,
|
| 57 |
+
n_times=800,
|
| 58 |
+
sfreq=200,
|
| 59 |
+
patch_size=200,
|
| 60 |
+
n_layer=16,
|
| 61 |
+
nhead=8,
|
| 62 |
+
dim_feedforward=1024,
|
| 63 |
+
channels_kernel_stride_padding_norm=(
|
| 64 |
+
(64, 49, 25, 24, (8, 64)),
|
| 65 |
+
(128, 3, 1, 1, (8, 128)),
|
| 66 |
+
(64, 3, 1, 1, (8, 64)),
|
| 67 |
+
),
|
| 68 |
+
n_channel_embeddings=19,
|
| 69 |
+
n_global_tokens=1,
|
| 70 |
+
global_token_layer=1,
|
| 71 |
+
drop_prob=0.1,
|
| 72 |
+
)
|
| 73 |
+
|
eegannotationtool/senua_eegdino_medium/preprocessing.py
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Exact four-second preprocessing used by the released SenuaLab models."""
|
| 2 |
+
|
| 3 |
+
from __future__ import annotations
|
| 4 |
+
|
| 5 |
+
import math
|
| 6 |
+
|
| 7 |
+
import numpy as np
|
| 8 |
+
from scipy.signal import butter, resample, resample_poly, sosfiltfilt
|
| 9 |
+
|
| 10 |
+
|
| 11 |
+
NUM_CHANNELS = 19
|
| 12 |
+
INTERNATIONAL_10_20_CHANNELS = [
|
| 13 |
+
"Fp1", "Fp2", "F3", "F4", "C3", "C4", "P3", "P4", "O1", "O2",
|
| 14 |
+
"F7", "F8", "T3", "T4", "T5", "T6", "Fz", "Cz", "Pz",
|
| 15 |
+
]
|
| 16 |
+
DATASET_29_CHANNEL_ORDER = INTERNATIONAL_10_20_CHANNELS
|
| 17 |
+
|
| 18 |
+
|
| 19 |
+
def normalize_sample(sample):
|
| 20 |
+
x = np.asarray(sample, dtype=np.float32)
|
| 21 |
+
mean = x.mean()
|
| 22 |
+
std = x.std()
|
| 23 |
+
return np.clip((x - mean) / max(float(std), 1e-7), -8.0, 8.0).astype(np.float32)
|
| 24 |
+
|
| 25 |
+
|
| 26 |
+
def preprocess_batch(
|
| 27 |
+
batch,
|
| 28 |
+
*,
|
| 29 |
+
source_sfreq=None,
|
| 30 |
+
target_sfreq=250,
|
| 31 |
+
target_samples=1000,
|
| 32 |
+
channel_names=None,
|
| 33 |
+
):
|
| 34 |
+
"""Filter, average-reference, resample, and globally normalize `[B,C,T]`."""
|
| 35 |
+
|
| 36 |
+
del channel_names # IED Finder has already arranged the declared channel order.
|
| 37 |
+
x = np.asarray(batch, dtype=np.float32)
|
| 38 |
+
if x.ndim == 2:
|
| 39 |
+
x = x[None]
|
| 40 |
+
if x.ndim != 3 or x.shape[1] != NUM_CHANNELS:
|
| 41 |
+
raise ValueError(f"Expected [batch,19,time], received {x.shape}")
|
| 42 |
+
sfreq = float(source_sfreq or target_sfreq)
|
| 43 |
+
if sfreq <= 90.0:
|
| 44 |
+
raise ValueError(f"Sampling rate must be above the 45 Hz cutoff; received {sfreq}")
|
| 45 |
+
sos = butter(4, [1.0, 45.0], btype="bandpass", fs=sfreq, output="sos")
|
| 46 |
+
x = sosfiltfilt(sos, x, axis=-1).astype(np.float32, copy=False)
|
| 47 |
+
x -= x.mean(axis=1, keepdims=True)
|
| 48 |
+
if not math.isclose(sfreq, float(target_sfreq)):
|
| 49 |
+
up = int(target_sfreq)
|
| 50 |
+
down = int(round(sfreq))
|
| 51 |
+
common = math.gcd(up, down)
|
| 52 |
+
x = resample_poly(x, up // common, down // common, axis=-1).astype(np.float32)
|
| 53 |
+
if x.shape[-1] != int(target_samples):
|
| 54 |
+
x = resample(x, int(target_samples), axis=-1).astype(np.float32)
|
| 55 |
+
mean = x.mean(axis=(1, 2), keepdims=True)
|
| 56 |
+
std = x.std(axis=(1, 2), keepdims=True)
|
| 57 |
+
return np.clip((x - mean) / np.maximum(std, 1e-7), -8.0, 8.0).astype(np.float32)
|
| 58 |
+
|
eegannotationtool/senua_eegpt/manifest.json
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"finder_defaults": {
|
| 3 |
+
"pad_policy": "skip",
|
| 4 |
+
"step_ms": 500.0,
|
| 5 |
+
"threshold": 0.8916015625
|
| 6 |
+
},
|
| 7 |
+
"important": "Research screening model; every candidate requires expert review. Threshold selected on public vEpiSet validation subjects.",
|
| 8 |
+
"input": {
|
| 9 |
+
"channel_aliases": {
|
| 10 |
+
"P7": "T5",
|
| 11 |
+
"P8": "T6",
|
| 12 |
+
"T7": "T3",
|
| 13 |
+
"T8": "T4"
|
| 14 |
+
},
|
| 15 |
+
"channel_names": [
|
| 16 |
+
"Fp1",
|
| 17 |
+
"Fp2",
|
| 18 |
+
"F3",
|
| 19 |
+
"F4",
|
| 20 |
+
"C3",
|
| 21 |
+
"C4",
|
| 22 |
+
"P3",
|
| 23 |
+
"P4",
|
| 24 |
+
"O1",
|
| 25 |
+
"O2",
|
| 26 |
+
"F7",
|
| 27 |
+
"F8",
|
| 28 |
+
"T3",
|
| 29 |
+
"T4",
|
| 30 |
+
"T5",
|
| 31 |
+
"T6",
|
| 32 |
+
"Fz",
|
| 33 |
+
"Cz",
|
| 34 |
+
"Pz"
|
| 35 |
+
],
|
| 36 |
+
"input_samples": 1000,
|
| 37 |
+
"normalization": "global four-second window z-score clipped to [-8,8]",
|
| 38 |
+
"reference": "common average",
|
| 39 |
+
"required_filters": [
|
| 40 |
+
"1-45 Hz zero-phase Butterworth"
|
| 41 |
+
],
|
| 42 |
+
"sampling_rate_hz": 250.0,
|
| 43 |
+
"window_seconds": 4.0
|
| 44 |
+
},
|
| 45 |
+
"name": "SenuaLab eegpt",
|
| 46 |
+
"schema_version": 1,
|
| 47 |
+
"source": "https://huggingface.co/SenuaLab/EEG-IED-Detection"
|
| 48 |
+
}
|
eegannotationtool/senua_eegpt/model.pth
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:9a0a55ed656049b4109d0e923ee0d461151a8a793995a24438717efecd2f24d5
|
| 3 |
+
size 102394167
|
eegannotationtool/senua_eegpt/model.py
ADDED
|
@@ -0,0 +1,116 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""EEG Annotation Tool adapter for the SenuaLab EEGPT temporal-head model."""
|
| 2 |
+
|
| 3 |
+
from __future__ import annotations
|
| 4 |
+
|
| 5 |
+
from typing import Any
|
| 6 |
+
|
| 7 |
+
import mne
|
| 8 |
+
import torch
|
| 9 |
+
from torch import nn
|
| 10 |
+
|
| 11 |
+
try:
|
| 12 |
+
from braindecode.models import EEGPT
|
| 13 |
+
except ImportError as exc: # imported by EEG Annotation Tool during discovery
|
| 14 |
+
raise ImportError("This model requires braindecode[hub]==1.6.1") from exc
|
| 15 |
+
|
| 16 |
+
from .preprocessing import INTERNATIONAL_10_20_CHANNELS, preprocess_batch as _preprocess
|
| 17 |
+
|
| 18 |
+
|
| 19 |
+
EEGPT_CHANNELS = [
|
| 20 |
+
"Fp1", "Fp2", "F3", "F4", "C3", "C4", "P3", "P4", "O1", "O2",
|
| 21 |
+
"F7", "F8", "T7", "T8", "P7", "P8", "Fz", "Cz", "Pz",
|
| 22 |
+
]
|
| 23 |
+
MODEL_CHANNELS = INTERNATIONAL_10_20_CHANNELS
|
| 24 |
+
MODEL_CHANNEL_ALIASES = {"T3": "T7", "T4": "T8", "T5": "P7", "T6": "P8"}
|
| 25 |
+
MODEL_INPUT_SAMPLES = 1000
|
| 26 |
+
MODEL_SAMPLING_RATE_HZ = 250.0
|
| 27 |
+
MODEL_WINDOW_SECONDS = 4.0
|
| 28 |
+
MODEL_ENTRY_CLASS = "SenuaEEGPT"
|
| 29 |
+
MODEL_NUM_CLASSES = 2
|
| 30 |
+
MODEL_CLASS_LABELS = ["Non-IED", "IED"]
|
| 31 |
+
MODEL_NON_IED_CLASS_INDEX = 0
|
| 32 |
+
MODEL_IED_CLASS_INDICES = [1]
|
| 33 |
+
MODEL_DESCRIPTION = "SenuaLab EEGPT encoder with an IED-specific temporal head"
|
| 34 |
+
MODEL_BATCH_PREPROCESSOR = "preprocess_batch"
|
| 35 |
+
MODEL_REQUIRED_REFERENCE = "common average (applied by model adapter)"
|
| 36 |
+
MODEL_REQUIRED_FILTERS = ["1-45 Hz zero-phase Butterworth (applied by model adapter)"]
|
| 37 |
+
MODEL_REQUIRED_NORMALIZATION = "global four-second window z-score, clipped to [-8,8]"
|
| 38 |
+
MODEL_INPUT_UNIT = "scale-invariant after window z-score"
|
| 39 |
+
MODEL_SOURCE_SIGNAL_POLICY = "raw"
|
| 40 |
+
MODEL_REQUIRES_FULL_WINDOW = True
|
| 41 |
+
MODEL_REQUIRES_ALL_CHANNELS = True
|
| 42 |
+
MODEL_DEFAULT_THRESHOLD = 0.8916015625
|
| 43 |
+
MODEL_DEFAULT_STEP_MS = 500.0
|
| 44 |
+
MODEL_DEFAULT_PAD_POLICY = "skip"
|
| 45 |
+
MODEL_DEFAULT_BATCH_SIZE = 16
|
| 46 |
+
MODEL_DEFAULT_BATCH_MEMORY_MB = 256.0
|
| 47 |
+
MODEL_VALIDATION_NOTE = "Threshold selected on vEpiSet validation subjects; requires Braindecode 1.6.1 and is not externally validated."
|
| 48 |
+
MODEL_PREPROCESSING_NOTE = "The adapter applies the released 1-45 Hz, common-average, resampling, and global-window z-score pipeline."
|
| 49 |
+
|
| 50 |
+
|
| 51 |
+
def preprocess_batch(batch, source_sfreq=None, channel_names=None):
|
| 52 |
+
return _preprocess(
|
| 53 |
+
batch,
|
| 54 |
+
source_sfreq=source_sfreq,
|
| 55 |
+
target_sfreq=250,
|
| 56 |
+
target_samples=1000,
|
| 57 |
+
channel_names=channel_names,
|
| 58 |
+
)
|
| 59 |
+
|
| 60 |
+
|
| 61 |
+
def _chs_info() -> list[dict[str, Any]]:
|
| 62 |
+
info = mne.create_info(EEGPT_CHANNELS, sfreq=250.0, ch_types="eeg")
|
| 63 |
+
info.set_montage("standard_1020")
|
| 64 |
+
return info["chs"]
|
| 65 |
+
|
| 66 |
+
|
| 67 |
+
class EEGPTIEDHead(nn.Module):
|
| 68 |
+
def __init__(self, hidden: int = 128, n_outputs: int = 2):
|
| 69 |
+
super().__init__()
|
| 70 |
+
flattened_embedding = 4 * 512
|
| 71 |
+
self.input_norm = nn.LayerNorm(flattened_embedding)
|
| 72 |
+
self.patch_projection = nn.Sequential(
|
| 73 |
+
nn.Linear(flattened_embedding, hidden), nn.GELU(), nn.Dropout(0.20)
|
| 74 |
+
)
|
| 75 |
+
self.temporal = nn.Sequential(
|
| 76 |
+
nn.Conv1d(hidden, hidden, 5, padding=2, groups=hidden, bias=False),
|
| 77 |
+
nn.BatchNorm1d(hidden),
|
| 78 |
+
nn.Conv1d(hidden, hidden, 1, bias=False),
|
| 79 |
+
nn.GELU(),
|
| 80 |
+
nn.Dropout(0.20),
|
| 81 |
+
nn.Conv1d(hidden, hidden, 3, padding=2, dilation=2, groups=hidden, bias=False),
|
| 82 |
+
nn.BatchNorm1d(hidden),
|
| 83 |
+
nn.Conv1d(hidden, hidden, 1, bias=False),
|
| 84 |
+
nn.GELU(),
|
| 85 |
+
)
|
| 86 |
+
self.attention = nn.Conv1d(hidden, 1, 1)
|
| 87 |
+
self.classifier = nn.Sequential(
|
| 88 |
+
nn.LayerNorm(hidden * 3), nn.Dropout(0.35), nn.Linear(hidden * 3, n_outputs)
|
| 89 |
+
)
|
| 90 |
+
|
| 91 |
+
def forward(self, z: torch.Tensor) -> torch.Tensor:
|
| 92 |
+
patches = self.input_norm(z.flatten(2))
|
| 93 |
+
patches = self.patch_projection(patches).transpose(1, 2)
|
| 94 |
+
patches = patches + self.temporal(patches)
|
| 95 |
+
weights = self.attention(patches).softmax(dim=-1)
|
| 96 |
+
pooled = torch.cat(
|
| 97 |
+
[(patches * weights).sum(-1), patches.mean(-1), patches.amax(-1)], dim=1
|
| 98 |
+
)
|
| 99 |
+
return self.classifier(pooled)
|
| 100 |
+
|
| 101 |
+
|
| 102 |
+
class SenuaEEGPT(EEGPT):
|
| 103 |
+
def __init__(self, num_channels: int = 19, num_classes: int = 2, input_length: int = 1000):
|
| 104 |
+
if num_channels != 19 or input_length != 1000:
|
| 105 |
+
raise ValueError("SenuaEEGPT requires 19 channels and 1,000 samples")
|
| 106 |
+
super().__init__(
|
| 107 |
+
n_outputs=num_classes,
|
| 108 |
+
n_chans=19,
|
| 109 |
+
chs_info=_chs_info(),
|
| 110 |
+
n_times=1000,
|
| 111 |
+
sfreq=250.0,
|
| 112 |
+
chan_proj_type="none",
|
| 113 |
+
return_encoder_output=False,
|
| 114 |
+
)
|
| 115 |
+
self.final_layer = EEGPTIEDHead(n_outputs=num_classes)
|
| 116 |
+
|
eegannotationtool/senua_eegpt/preprocessing.py
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Exact four-second preprocessing used by the released SenuaLab models."""
|
| 2 |
+
|
| 3 |
+
from __future__ import annotations
|
| 4 |
+
|
| 5 |
+
import math
|
| 6 |
+
|
| 7 |
+
import numpy as np
|
| 8 |
+
from scipy.signal import butter, resample, resample_poly, sosfiltfilt
|
| 9 |
+
|
| 10 |
+
|
| 11 |
+
NUM_CHANNELS = 19
|
| 12 |
+
INTERNATIONAL_10_20_CHANNELS = [
|
| 13 |
+
"Fp1", "Fp2", "F3", "F4", "C3", "C4", "P3", "P4", "O1", "O2",
|
| 14 |
+
"F7", "F8", "T3", "T4", "T5", "T6", "Fz", "Cz", "Pz",
|
| 15 |
+
]
|
| 16 |
+
DATASET_29_CHANNEL_ORDER = INTERNATIONAL_10_20_CHANNELS
|
| 17 |
+
|
| 18 |
+
|
| 19 |
+
def normalize_sample(sample):
|
| 20 |
+
x = np.asarray(sample, dtype=np.float32)
|
| 21 |
+
mean = x.mean()
|
| 22 |
+
std = x.std()
|
| 23 |
+
return np.clip((x - mean) / max(float(std), 1e-7), -8.0, 8.0).astype(np.float32)
|
| 24 |
+
|
| 25 |
+
|
| 26 |
+
def preprocess_batch(
|
| 27 |
+
batch,
|
| 28 |
+
*,
|
| 29 |
+
source_sfreq=None,
|
| 30 |
+
target_sfreq=250,
|
| 31 |
+
target_samples=1000,
|
| 32 |
+
channel_names=None,
|
| 33 |
+
):
|
| 34 |
+
"""Filter, average-reference, resample, and globally normalize `[B,C,T]`."""
|
| 35 |
+
|
| 36 |
+
del channel_names # IED Finder has already arranged the declared channel order.
|
| 37 |
+
x = np.asarray(batch, dtype=np.float32)
|
| 38 |
+
if x.ndim == 2:
|
| 39 |
+
x = x[None]
|
| 40 |
+
if x.ndim != 3 or x.shape[1] != NUM_CHANNELS:
|
| 41 |
+
raise ValueError(f"Expected [batch,19,time], received {x.shape}")
|
| 42 |
+
sfreq = float(source_sfreq or target_sfreq)
|
| 43 |
+
if sfreq <= 90.0:
|
| 44 |
+
raise ValueError(f"Sampling rate must be above the 45 Hz cutoff; received {sfreq}")
|
| 45 |
+
sos = butter(4, [1.0, 45.0], btype="bandpass", fs=sfreq, output="sos")
|
| 46 |
+
x = sosfiltfilt(sos, x, axis=-1).astype(np.float32, copy=False)
|
| 47 |
+
x -= x.mean(axis=1, keepdims=True)
|
| 48 |
+
if not math.isclose(sfreq, float(target_sfreq)):
|
| 49 |
+
up = int(target_sfreq)
|
| 50 |
+
down = int(round(sfreq))
|
| 51 |
+
common = math.gcd(up, down)
|
| 52 |
+
x = resample_poly(x, up // common, down // common, axis=-1).astype(np.float32)
|
| 53 |
+
if x.shape[-1] != int(target_samples):
|
| 54 |
+
x = resample(x, int(target_samples), axis=-1).astype(np.float32)
|
| 55 |
+
mean = x.mean(axis=(1, 2), keepdims=True)
|
| 56 |
+
std = x.std(axis=(1, 2), keepdims=True)
|
| 57 |
+
return np.clip((x - mean) / np.maximum(std, 1e-7), -8.0, 8.0).astype(np.float32)
|
| 58 |
+
|
eegannotationtool/senua_eegpt_linear_probe/manifest.json
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"finder_defaults": {
|
| 3 |
+
"pad_policy": "skip",
|
| 4 |
+
"step_ms": 500.0,
|
| 5 |
+
"threshold": 0.40234375
|
| 6 |
+
},
|
| 7 |
+
"important": "Research screening model; every candidate requires expert review. Threshold selected on public vEpiSet validation subjects.",
|
| 8 |
+
"input": {
|
| 9 |
+
"channel_aliases": {
|
| 10 |
+
"P7": "T5",
|
| 11 |
+
"P8": "T6",
|
| 12 |
+
"T7": "T3",
|
| 13 |
+
"T8": "T4"
|
| 14 |
+
},
|
| 15 |
+
"channel_names": [
|
| 16 |
+
"Fp1",
|
| 17 |
+
"Fp2",
|
| 18 |
+
"F3",
|
| 19 |
+
"F4",
|
| 20 |
+
"C3",
|
| 21 |
+
"C4",
|
| 22 |
+
"P3",
|
| 23 |
+
"P4",
|
| 24 |
+
"O1",
|
| 25 |
+
"O2",
|
| 26 |
+
"F7",
|
| 27 |
+
"F8",
|
| 28 |
+
"T3",
|
| 29 |
+
"T4",
|
| 30 |
+
"T5",
|
| 31 |
+
"T6",
|
| 32 |
+
"Fz",
|
| 33 |
+
"Cz",
|
| 34 |
+
"Pz"
|
| 35 |
+
],
|
| 36 |
+
"input_samples": 1000,
|
| 37 |
+
"normalization": "global four-second window z-score clipped to [-8,8]",
|
| 38 |
+
"reference": "common average",
|
| 39 |
+
"required_filters": [
|
| 40 |
+
"1-45 Hz zero-phase Butterworth"
|
| 41 |
+
],
|
| 42 |
+
"sampling_rate_hz": 250.0,
|
| 43 |
+
"window_seconds": 4.0
|
| 44 |
+
},
|
| 45 |
+
"name": "SenuaLab eegpt_linear_probe",
|
| 46 |
+
"schema_version": 1,
|
| 47 |
+
"source": "https://huggingface.co/SenuaLab/EEG-IED-Detection"
|
| 48 |
+
}
|
eegannotationtool/senua_eegpt_linear_probe/model.pth
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:a51fd32505d51d80677ed6504b94d0b8a6718cb1de21a07cd3253ed1e1a3405a
|
| 3 |
+
size 101312967
|
eegannotationtool/senua_eegpt_linear_probe/model.py
ADDED
|
@@ -0,0 +1,77 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""EEG Annotation Tool adapter for the experimental SenuaLab EEGPT linear probe."""
|
| 2 |
+
|
| 3 |
+
from __future__ import annotations
|
| 4 |
+
|
| 5 |
+
from typing import Any
|
| 6 |
+
|
| 7 |
+
import mne
|
| 8 |
+
|
| 9 |
+
try:
|
| 10 |
+
from braindecode.models import EEGPT
|
| 11 |
+
except ImportError as exc:
|
| 12 |
+
raise ImportError("This model requires braindecode[hub]==1.6.1") from exc
|
| 13 |
+
|
| 14 |
+
from .preprocessing import INTERNATIONAL_10_20_CHANNELS, preprocess_batch as _preprocess
|
| 15 |
+
|
| 16 |
+
|
| 17 |
+
EEGPT_CHANNELS = [
|
| 18 |
+
"Fp1", "Fp2", "F3", "F4", "C3", "C4", "P3", "P4", "O1", "O2",
|
| 19 |
+
"F7", "F8", "T7", "T8", "P7", "P8", "Fz", "Cz", "Pz",
|
| 20 |
+
]
|
| 21 |
+
MODEL_CHANNELS = INTERNATIONAL_10_20_CHANNELS
|
| 22 |
+
MODEL_CHANNEL_ALIASES = {"T3": "T7", "T4": "T8", "T5": "P7", "T6": "P8"}
|
| 23 |
+
MODEL_INPUT_SAMPLES = 1000
|
| 24 |
+
MODEL_SAMPLING_RATE_HZ = 250.0
|
| 25 |
+
MODEL_WINDOW_SECONDS = 4.0
|
| 26 |
+
MODEL_ENTRY_CLASS = "SenuaEEGPTLinearProbe"
|
| 27 |
+
MODEL_NUM_CLASSES = 2
|
| 28 |
+
MODEL_CLASS_LABELS = ["Non-IED", "IED"]
|
| 29 |
+
MODEL_NON_IED_CLASS_INDEX = 0
|
| 30 |
+
MODEL_IED_CLASS_INDICES = [1]
|
| 31 |
+
MODEL_DESCRIPTION = "Experimental SenuaLab EEGPT frozen-encoder linear probe"
|
| 32 |
+
MODEL_BATCH_PREPROCESSOR = "preprocess_batch"
|
| 33 |
+
MODEL_REQUIRED_REFERENCE = "common average (applied by model adapter)"
|
| 34 |
+
MODEL_REQUIRED_FILTERS = ["1-45 Hz zero-phase Butterworth (applied by model adapter)"]
|
| 35 |
+
MODEL_REQUIRED_NORMALIZATION = "global four-second window z-score, clipped to [-8,8]"
|
| 36 |
+
MODEL_INPUT_UNIT = "scale-invariant after window z-score"
|
| 37 |
+
MODEL_SOURCE_SIGNAL_POLICY = "raw"
|
| 38 |
+
MODEL_REQUIRES_FULL_WINDOW = True
|
| 39 |
+
MODEL_REQUIRES_ALL_CHANNELS = True
|
| 40 |
+
MODEL_DEFAULT_THRESHOLD = 0.40234375
|
| 41 |
+
MODEL_DEFAULT_STEP_MS = 500.0
|
| 42 |
+
MODEL_DEFAULT_PAD_POLICY = "skip"
|
| 43 |
+
MODEL_DEFAULT_BATCH_SIZE = 16
|
| 44 |
+
MODEL_DEFAULT_BATCH_MEMORY_MB = 256.0
|
| 45 |
+
MODEL_VALIDATION_NOTE = "Experimental ablation; not recommended for deployment and not externally validated."
|
| 46 |
+
|
| 47 |
+
|
| 48 |
+
def preprocess_batch(batch, source_sfreq=None, channel_names=None):
|
| 49 |
+
return _preprocess(
|
| 50 |
+
batch,
|
| 51 |
+
source_sfreq=source_sfreq,
|
| 52 |
+
target_sfreq=250,
|
| 53 |
+
target_samples=1000,
|
| 54 |
+
channel_names=channel_names,
|
| 55 |
+
)
|
| 56 |
+
|
| 57 |
+
|
| 58 |
+
def _chs_info() -> list[dict[str, Any]]:
|
| 59 |
+
info = mne.create_info(EEGPT_CHANNELS, sfreq=250.0, ch_types="eeg")
|
| 60 |
+
info.set_montage("standard_1020")
|
| 61 |
+
return info["chs"]
|
| 62 |
+
|
| 63 |
+
|
| 64 |
+
class SenuaEEGPTLinearProbe(EEGPT):
|
| 65 |
+
def __init__(self, num_channels: int = 19, num_classes: int = 2, input_length: int = 1000):
|
| 66 |
+
if num_channels != 19 or input_length != 1000:
|
| 67 |
+
raise ValueError("SenuaEEGPTLinearProbe requires 19 channels and 1,000 samples")
|
| 68 |
+
super().__init__(
|
| 69 |
+
n_outputs=num_classes,
|
| 70 |
+
n_chans=19,
|
| 71 |
+
chs_info=_chs_info(),
|
| 72 |
+
n_times=1000,
|
| 73 |
+
sfreq=250.0,
|
| 74 |
+
chan_proj_type="none",
|
| 75 |
+
return_encoder_output=False,
|
| 76 |
+
)
|
| 77 |
+
|
eegannotationtool/senua_eegpt_linear_probe/preprocessing.py
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Exact four-second preprocessing used by the released SenuaLab models."""
|
| 2 |
+
|
| 3 |
+
from __future__ import annotations
|
| 4 |
+
|
| 5 |
+
import math
|
| 6 |
+
|
| 7 |
+
import numpy as np
|
| 8 |
+
from scipy.signal import butter, resample, resample_poly, sosfiltfilt
|
| 9 |
+
|
| 10 |
+
|
| 11 |
+
NUM_CHANNELS = 19
|
| 12 |
+
INTERNATIONAL_10_20_CHANNELS = [
|
| 13 |
+
"Fp1", "Fp2", "F3", "F4", "C3", "C4", "P3", "P4", "O1", "O2",
|
| 14 |
+
"F7", "F8", "T3", "T4", "T5", "T6", "Fz", "Cz", "Pz",
|
| 15 |
+
]
|
| 16 |
+
DATASET_29_CHANNEL_ORDER = INTERNATIONAL_10_20_CHANNELS
|
| 17 |
+
|
| 18 |
+
|
| 19 |
+
def normalize_sample(sample):
|
| 20 |
+
x = np.asarray(sample, dtype=np.float32)
|
| 21 |
+
mean = x.mean()
|
| 22 |
+
std = x.std()
|
| 23 |
+
return np.clip((x - mean) / max(float(std), 1e-7), -8.0, 8.0).astype(np.float32)
|
| 24 |
+
|
| 25 |
+
|
| 26 |
+
def preprocess_batch(
|
| 27 |
+
batch,
|
| 28 |
+
*,
|
| 29 |
+
source_sfreq=None,
|
| 30 |
+
target_sfreq=250,
|
| 31 |
+
target_samples=1000,
|
| 32 |
+
channel_names=None,
|
| 33 |
+
):
|
| 34 |
+
"""Filter, average-reference, resample, and globally normalize `[B,C,T]`."""
|
| 35 |
+
|
| 36 |
+
del channel_names # IED Finder has already arranged the declared channel order.
|
| 37 |
+
x = np.asarray(batch, dtype=np.float32)
|
| 38 |
+
if x.ndim == 2:
|
| 39 |
+
x = x[None]
|
| 40 |
+
if x.ndim != 3 or x.shape[1] != NUM_CHANNELS:
|
| 41 |
+
raise ValueError(f"Expected [batch,19,time], received {x.shape}")
|
| 42 |
+
sfreq = float(source_sfreq or target_sfreq)
|
| 43 |
+
if sfreq <= 90.0:
|
| 44 |
+
raise ValueError(f"Sampling rate must be above the 45 Hz cutoff; received {sfreq}")
|
| 45 |
+
sos = butter(4, [1.0, 45.0], btype="bandpass", fs=sfreq, output="sos")
|
| 46 |
+
x = sosfiltfilt(sos, x, axis=-1).astype(np.float32, copy=False)
|
| 47 |
+
x -= x.mean(axis=1, keepdims=True)
|
| 48 |
+
if not math.isclose(sfreq, float(target_sfreq)):
|
| 49 |
+
up = int(target_sfreq)
|
| 50 |
+
down = int(round(sfreq))
|
| 51 |
+
common = math.gcd(up, down)
|
| 52 |
+
x = resample_poly(x, up // common, down // common, axis=-1).astype(np.float32)
|
| 53 |
+
if x.shape[-1] != int(target_samples):
|
| 54 |
+
x = resample(x, int(target_samples), axis=-1).astype(np.float32)
|
| 55 |
+
mean = x.mean(axis=(1, 2), keepdims=True)
|
| 56 |
+
std = x.std(axis=(1, 2), keepdims=True)
|
| 57 |
+
return np.clip((x - mean) / np.maximum(std, 1e-7), -8.0, 8.0).astype(np.float32)
|
| 58 |
+
|
eegannotationtool/senua_iednet_lite/manifest.json
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"finder_defaults": {
|
| 3 |
+
"pad_policy": "skip",
|
| 4 |
+
"step_ms": 500.0,
|
| 5 |
+
"threshold": 0.6004377007484436
|
| 6 |
+
},
|
| 7 |
+
"important": "Research screening model; every candidate requires expert review. Threshold selected on public vEpiSet validation subjects.",
|
| 8 |
+
"input": {
|
| 9 |
+
"channel_aliases": {
|
| 10 |
+
"P7": "T5",
|
| 11 |
+
"P8": "T6",
|
| 12 |
+
"T7": "T3",
|
| 13 |
+
"T8": "T4"
|
| 14 |
+
},
|
| 15 |
+
"channel_names": [
|
| 16 |
+
"Fp1",
|
| 17 |
+
"Fp2",
|
| 18 |
+
"F3",
|
| 19 |
+
"F4",
|
| 20 |
+
"C3",
|
| 21 |
+
"C4",
|
| 22 |
+
"P3",
|
| 23 |
+
"P4",
|
| 24 |
+
"O1",
|
| 25 |
+
"O2",
|
| 26 |
+
"F7",
|
| 27 |
+
"F8",
|
| 28 |
+
"T3",
|
| 29 |
+
"T4",
|
| 30 |
+
"T5",
|
| 31 |
+
"T6",
|
| 32 |
+
"Fz",
|
| 33 |
+
"Cz",
|
| 34 |
+
"Pz"
|
| 35 |
+
],
|
| 36 |
+
"input_samples": 1000,
|
| 37 |
+
"normalization": "global four-second window z-score clipped to [-8,8]",
|
| 38 |
+
"reference": "common average",
|
| 39 |
+
"required_filters": [
|
| 40 |
+
"1-45 Hz zero-phase Butterworth"
|
| 41 |
+
],
|
| 42 |
+
"sampling_rate_hz": 250.0,
|
| 43 |
+
"window_seconds": 4.0
|
| 44 |
+
},
|
| 45 |
+
"name": "SenuaLab iednet_lite",
|
| 46 |
+
"schema_version": 1,
|
| 47 |
+
"source": "https://huggingface.co/SenuaLab/EEG-IED-Detection"
|
| 48 |
+
}
|
eegannotationtool/senua_iednet_lite/model.pth
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:383fe1de4aa8cf01b5f1c0a6734d92214ec0e7f96a6cd18a50fa52c52ccb210c
|
| 3 |
+
size 1041083
|
eegannotationtool/senua_iednet_lite/model.py
ADDED
|
@@ -0,0 +1,119 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""EEG Annotation Tool adapter for SenuaLab IEDNet-Lite."""
|
| 2 |
+
|
| 3 |
+
from __future__ import annotations
|
| 4 |
+
|
| 5 |
+
import torch
|
| 6 |
+
import torch.nn.functional as F
|
| 7 |
+
from torch import nn
|
| 8 |
+
|
| 9 |
+
from .preprocessing import INTERNATIONAL_10_20_CHANNELS, preprocess_batch as _preprocess
|
| 10 |
+
|
| 11 |
+
|
| 12 |
+
MODEL_CHANNELS = INTERNATIONAL_10_20_CHANNELS
|
| 13 |
+
MODEL_INPUT_SAMPLES = 1000
|
| 14 |
+
MODEL_SAMPLING_RATE_HZ = 250.0
|
| 15 |
+
MODEL_WINDOW_SECONDS = 4.0
|
| 16 |
+
MODEL_ENTRY_CLASS = "IEDNetLite"
|
| 17 |
+
MODEL_NUM_CLASSES = 2
|
| 18 |
+
MODEL_CLASS_LABELS = ["Non-IED", "IED"]
|
| 19 |
+
MODEL_NON_IED_CLASS_INDEX = 0
|
| 20 |
+
MODEL_IED_CLASS_INDICES = [1]
|
| 21 |
+
MODEL_DESCRIPTION = "SenuaLab patient-independent IEDNet-Lite binary IED detector"
|
| 22 |
+
MODEL_BATCH_PREPROCESSOR = "preprocess_batch"
|
| 23 |
+
MODEL_REQUIRED_REFERENCE = "common average (applied by model adapter)"
|
| 24 |
+
MODEL_REQUIRED_FILTERS = ["1-45 Hz zero-phase Butterworth (applied by model adapter)"]
|
| 25 |
+
MODEL_REQUIRED_NORMALIZATION = "global four-second window z-score, clipped to [-8,8]"
|
| 26 |
+
MODEL_INPUT_UNIT = "scale-invariant after window z-score"
|
| 27 |
+
MODEL_SOURCE_SIGNAL_POLICY = "raw"
|
| 28 |
+
MODEL_REQUIRES_FULL_WINDOW = True
|
| 29 |
+
MODEL_REQUIRES_ALL_CHANNELS = True
|
| 30 |
+
MODEL_DEFAULT_THRESHOLD = 0.6004377007484436
|
| 31 |
+
MODEL_DEFAULT_STEP_MS = 500.0
|
| 32 |
+
MODEL_DEFAULT_PAD_POLICY = "skip"
|
| 33 |
+
MODEL_DEFAULT_BATCH_SIZE = 64
|
| 34 |
+
MODEL_DEFAULT_BATCH_MEMORY_MB = 64.0
|
| 35 |
+
MODEL_VALIDATION_NOTE = "Threshold selected on the vEpiSet validation subjects; not externally validated."
|
| 36 |
+
MODEL_PREPROCESSING_NOTE = "The adapter applies the exact released 1-45 Hz, common-average, resampling, and global-window z-score pipeline."
|
| 37 |
+
|
| 38 |
+
|
| 39 |
+
def preprocess_batch(batch, source_sfreq=None, channel_names=None):
|
| 40 |
+
return _preprocess(
|
| 41 |
+
batch,
|
| 42 |
+
source_sfreq=source_sfreq,
|
| 43 |
+
target_sfreq=250,
|
| 44 |
+
target_samples=1000,
|
| 45 |
+
channel_names=channel_names,
|
| 46 |
+
)
|
| 47 |
+
|
| 48 |
+
|
| 49 |
+
class SqueezeExcitation(nn.Module):
|
| 50 |
+
def __init__(self, channels: int, reduction: int = 8):
|
| 51 |
+
super().__init__()
|
| 52 |
+
hidden = max(8, channels // reduction)
|
| 53 |
+
self.net = nn.Sequential(
|
| 54 |
+
nn.AdaptiveAvgPool1d(1),
|
| 55 |
+
nn.Conv1d(channels, hidden, 1),
|
| 56 |
+
nn.GELU(),
|
| 57 |
+
nn.Conv1d(hidden, channels, 1),
|
| 58 |
+
nn.Sigmoid(),
|
| 59 |
+
)
|
| 60 |
+
|
| 61 |
+
def forward(self, x):
|
| 62 |
+
return x * self.net(x)
|
| 63 |
+
|
| 64 |
+
|
| 65 |
+
class DSResidual(nn.Module):
|
| 66 |
+
def __init__(self, in_ch: int, out_ch: int, kernel: int, stride: int = 1, dilation: int = 1):
|
| 67 |
+
super().__init__()
|
| 68 |
+
pad = dilation * (kernel - 1) // 2
|
| 69 |
+
self.body = nn.Sequential(
|
| 70 |
+
nn.Conv1d(in_ch, in_ch, kernel, stride=stride, padding=pad, dilation=dilation, groups=in_ch, bias=False),
|
| 71 |
+
nn.BatchNorm1d(in_ch),
|
| 72 |
+
nn.GELU(),
|
| 73 |
+
nn.Conv1d(in_ch, out_ch, 1, bias=False),
|
| 74 |
+
nn.BatchNorm1d(out_ch),
|
| 75 |
+
SqueezeExcitation(out_ch),
|
| 76 |
+
)
|
| 77 |
+
self.skip = (
|
| 78 |
+
nn.Identity()
|
| 79 |
+
if in_ch == out_ch and stride == 1
|
| 80 |
+
else nn.Sequential(nn.Conv1d(in_ch, out_ch, 1, stride=stride, bias=False), nn.BatchNorm1d(out_ch))
|
| 81 |
+
)
|
| 82 |
+
|
| 83 |
+
def forward(self, x):
|
| 84 |
+
return F.gelu(self.body(x) + self.skip(x))
|
| 85 |
+
|
| 86 |
+
|
| 87 |
+
class IEDNetLite(nn.Module):
|
| 88 |
+
def __init__(self, num_channels: int = 19, num_classes: int = 2, input_length: int = 1000, subtypes: int = 5):
|
| 89 |
+
super().__init__()
|
| 90 |
+
del input_length
|
| 91 |
+
branches = []
|
| 92 |
+
for kernel in (7, 15, 31):
|
| 93 |
+
branches.append(
|
| 94 |
+
nn.Sequential(
|
| 95 |
+
nn.Conv1d(num_channels, 24, kernel, stride=2, padding=kernel // 2, bias=False),
|
| 96 |
+
nn.BatchNorm1d(24),
|
| 97 |
+
nn.GELU(),
|
| 98 |
+
)
|
| 99 |
+
)
|
| 100 |
+
self.stem = nn.ModuleList(branches)
|
| 101 |
+
self.backbone = nn.Sequential(
|
| 102 |
+
DSResidual(72, 72, 9, stride=2),
|
| 103 |
+
DSResidual(72, 96, 7, stride=2),
|
| 104 |
+
DSResidual(96, 96, 7, dilation=2),
|
| 105 |
+
DSResidual(96, 144, 5, stride=2),
|
| 106 |
+
DSResidual(144, 144, 5, dilation=2),
|
| 107 |
+
DSResidual(144, 192, 3, stride=2),
|
| 108 |
+
DSResidual(192, 192, 3, dilation=2),
|
| 109 |
+
)
|
| 110 |
+
self.dropout = nn.Dropout(0.35)
|
| 111 |
+
self.binary_head = nn.Linear(384, num_classes)
|
| 112 |
+
self.subtype_head = nn.Linear(384, subtypes)
|
| 113 |
+
|
| 114 |
+
def forward(self, x):
|
| 115 |
+
x = torch.cat([branch(x) for branch in self.stem], dim=1)
|
| 116 |
+
x = self.backbone(x)
|
| 117 |
+
features = self.dropout(torch.cat([x.mean(-1), x.amax(-1)], dim=1))
|
| 118 |
+
return self.binary_head(features), self.subtype_head(features)
|
| 119 |
+
|
eegannotationtool/senua_iednet_lite/preprocessing.py
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Exact four-second preprocessing used by the released SenuaLab models."""
|
| 2 |
+
|
| 3 |
+
from __future__ import annotations
|
| 4 |
+
|
| 5 |
+
import math
|
| 6 |
+
|
| 7 |
+
import numpy as np
|
| 8 |
+
from scipy.signal import butter, resample, resample_poly, sosfiltfilt
|
| 9 |
+
|
| 10 |
+
|
| 11 |
+
NUM_CHANNELS = 19
|
| 12 |
+
INTERNATIONAL_10_20_CHANNELS = [
|
| 13 |
+
"Fp1", "Fp2", "F3", "F4", "C3", "C4", "P3", "P4", "O1", "O2",
|
| 14 |
+
"F7", "F8", "T3", "T4", "T5", "T6", "Fz", "Cz", "Pz",
|
| 15 |
+
]
|
| 16 |
+
DATASET_29_CHANNEL_ORDER = INTERNATIONAL_10_20_CHANNELS
|
| 17 |
+
|
| 18 |
+
|
| 19 |
+
def normalize_sample(sample):
|
| 20 |
+
x = np.asarray(sample, dtype=np.float32)
|
| 21 |
+
mean = x.mean()
|
| 22 |
+
std = x.std()
|
| 23 |
+
return np.clip((x - mean) / max(float(std), 1e-7), -8.0, 8.0).astype(np.float32)
|
| 24 |
+
|
| 25 |
+
|
| 26 |
+
def preprocess_batch(
|
| 27 |
+
batch,
|
| 28 |
+
*,
|
| 29 |
+
source_sfreq=None,
|
| 30 |
+
target_sfreq=250,
|
| 31 |
+
target_samples=1000,
|
| 32 |
+
channel_names=None,
|
| 33 |
+
):
|
| 34 |
+
"""Filter, average-reference, resample, and globally normalize `[B,C,T]`."""
|
| 35 |
+
|
| 36 |
+
del channel_names # IED Finder has already arranged the declared channel order.
|
| 37 |
+
x = np.asarray(batch, dtype=np.float32)
|
| 38 |
+
if x.ndim == 2:
|
| 39 |
+
x = x[None]
|
| 40 |
+
if x.ndim != 3 or x.shape[1] != NUM_CHANNELS:
|
| 41 |
+
raise ValueError(f"Expected [batch,19,time], received {x.shape}")
|
| 42 |
+
sfreq = float(source_sfreq or target_sfreq)
|
| 43 |
+
if sfreq <= 90.0:
|
| 44 |
+
raise ValueError(f"Sampling rate must be above the 45 Hz cutoff; received {sfreq}")
|
| 45 |
+
sos = butter(4, [1.0, 45.0], btype="bandpass", fs=sfreq, output="sos")
|
| 46 |
+
x = sosfiltfilt(sos, x, axis=-1).astype(np.float32, copy=False)
|
| 47 |
+
x -= x.mean(axis=1, keepdims=True)
|
| 48 |
+
if not math.isclose(sfreq, float(target_sfreq)):
|
| 49 |
+
up = int(target_sfreq)
|
| 50 |
+
down = int(round(sfreq))
|
| 51 |
+
common = math.gcd(up, down)
|
| 52 |
+
x = resample_poly(x, up // common, down // common, axis=-1).astype(np.float32)
|
| 53 |
+
if x.shape[-1] != int(target_samples):
|
| 54 |
+
x = resample(x, int(target_samples), axis=-1).astype(np.float32)
|
| 55 |
+
mean = x.mean(axis=(1, 2), keepdims=True)
|
| 56 |
+
std = x.std(axis=(1, 2), keepdims=True)
|
| 57 |
+
return np.clip((x - mean) / np.maximum(std, 1e-7), -8.0, 8.0).astype(np.float32)
|
| 58 |
+
|
eegannotationtool/senua_resnet_attention/manifest.json
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"finder_defaults": {
|
| 3 |
+
"pad_policy": "skip",
|
| 4 |
+
"step_ms": 500.0,
|
| 5 |
+
"threshold": 0.6944227814674377
|
| 6 |
+
},
|
| 7 |
+
"important": "Research screening model; every candidate requires expert review. Threshold selected on public vEpiSet validation subjects.",
|
| 8 |
+
"input": {
|
| 9 |
+
"channel_aliases": {
|
| 10 |
+
"P7": "T5",
|
| 11 |
+
"P8": "T6",
|
| 12 |
+
"T7": "T3",
|
| 13 |
+
"T8": "T4"
|
| 14 |
+
},
|
| 15 |
+
"channel_names": [
|
| 16 |
+
"Fp1",
|
| 17 |
+
"Fp2",
|
| 18 |
+
"F3",
|
| 19 |
+
"F4",
|
| 20 |
+
"C3",
|
| 21 |
+
"C4",
|
| 22 |
+
"P3",
|
| 23 |
+
"P4",
|
| 24 |
+
"O1",
|
| 25 |
+
"O2",
|
| 26 |
+
"F7",
|
| 27 |
+
"F8",
|
| 28 |
+
"T3",
|
| 29 |
+
"T4",
|
| 30 |
+
"T5",
|
| 31 |
+
"T6",
|
| 32 |
+
"Fz",
|
| 33 |
+
"Cz",
|
| 34 |
+
"Pz"
|
| 35 |
+
],
|
| 36 |
+
"input_samples": 1000,
|
| 37 |
+
"normalization": "global four-second window z-score clipped to [-8,8]",
|
| 38 |
+
"reference": "common average",
|
| 39 |
+
"required_filters": [
|
| 40 |
+
"1-45 Hz zero-phase Butterworth"
|
| 41 |
+
],
|
| 42 |
+
"sampling_rate_hz": 250.0,
|
| 43 |
+
"window_seconds": 4.0
|
| 44 |
+
},
|
| 45 |
+
"name": "SenuaLab resnet_attention",
|
| 46 |
+
"schema_version": 1,
|
| 47 |
+
"source": "https://huggingface.co/SenuaLab/EEG-IED-Detection"
|
| 48 |
+
}
|
eegannotationtool/senua_resnet_attention/model.pth
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:df088c4fd087b453ef3aca76f54a975a69f015324f8d862d2d4ba5316cb91e1a
|
| 3 |
+
size 20037039
|
eegannotationtool/senua_resnet_attention/model.py
ADDED
|
@@ -0,0 +1,677 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import torch
|
| 2 |
+
import torch.nn as nn
|
| 3 |
+
import torch.nn.functional as F
|
| 4 |
+
import math
|
| 5 |
+
|
| 6 |
+
# Import preprocessing constants
|
| 7 |
+
from .preprocessing import INTERNATIONAL_10_20_CHANNELS, NUM_CHANNELS, preprocess_batch as _preprocess
|
| 8 |
+
|
| 9 |
+
|
| 10 |
+
MODEL_CHANNELS = INTERNATIONAL_10_20_CHANNELS
|
| 11 |
+
MODEL_INPUT_SAMPLES = 1000
|
| 12 |
+
MODEL_SAMPLING_RATE_HZ = 250.0
|
| 13 |
+
MODEL_WINDOW_SECONDS = 4.0
|
| 14 |
+
MODEL_ENTRY_CLASS = "EEGResNetAttention"
|
| 15 |
+
MODEL_NUM_CLASSES = 2
|
| 16 |
+
MODEL_CLASS_LABELS = ["Non-IED", "IED"]
|
| 17 |
+
MODEL_NON_IED_CLASS_INDEX = 0
|
| 18 |
+
MODEL_IED_CLASS_INDICES = [1]
|
| 19 |
+
MODEL_DESCRIPTION = "SenuaLab patient-independent ResNet-Attention binary IED detector"
|
| 20 |
+
MODEL_BATCH_PREPROCESSOR = "preprocess_batch"
|
| 21 |
+
MODEL_REQUIRED_REFERENCE = "common average (applied by model adapter)"
|
| 22 |
+
MODEL_REQUIRED_FILTERS = ["1-45 Hz zero-phase Butterworth (applied by model adapter)"]
|
| 23 |
+
MODEL_REQUIRED_NORMALIZATION = "global four-second window z-score, clipped to [-8,8]"
|
| 24 |
+
MODEL_INPUT_UNIT = "scale-invariant after window z-score"
|
| 25 |
+
MODEL_SOURCE_SIGNAL_POLICY = "raw"
|
| 26 |
+
MODEL_REQUIRES_FULL_WINDOW = True
|
| 27 |
+
MODEL_REQUIRES_ALL_CHANNELS = True
|
| 28 |
+
MODEL_DEFAULT_THRESHOLD = 0.6944227814674377
|
| 29 |
+
MODEL_DEFAULT_STEP_MS = 500.0
|
| 30 |
+
MODEL_DEFAULT_PAD_POLICY = "skip"
|
| 31 |
+
MODEL_DEFAULT_BATCH_SIZE = 32
|
| 32 |
+
MODEL_DEFAULT_BATCH_MEMORY_MB = 96.0
|
| 33 |
+
MODEL_VALIDATION_NOTE = "Threshold selected on the vEpiSet validation subjects; not externally validated."
|
| 34 |
+
MODEL_PREPROCESSING_NOTE = "The adapter applies the exact released 1-45 Hz, common-average, resampling, and global-window z-score pipeline."
|
| 35 |
+
|
| 36 |
+
|
| 37 |
+
def preprocess_batch(batch, source_sfreq=None, channel_names=None):
|
| 38 |
+
return _preprocess(
|
| 39 |
+
batch,
|
| 40 |
+
source_sfreq=source_sfreq,
|
| 41 |
+
target_sfreq=250,
|
| 42 |
+
target_samples=1000,
|
| 43 |
+
channel_names=channel_names,
|
| 44 |
+
)
|
| 45 |
+
|
| 46 |
+
|
| 47 |
+
class Simple1DCNN(nn.Module):
|
| 48 |
+
"""
|
| 49 |
+
Simple 1D CNN for EEG IED classification.
|
| 50 |
+
|
| 51 |
+
Architecture:
|
| 52 |
+
- 4 convolutional blocks with BatchNorm, ReLU, and MaxPool
|
| 53 |
+
- Fully connected classifier with dropout
|
| 54 |
+
|
| 55 |
+
Default configuration uses 19 channels (standard 10-20) to match
|
| 56 |
+
the vEpiSet preprocessing pipeline.
|
| 57 |
+
"""
|
| 58 |
+
|
| 59 |
+
def __init__(self, num_channels=NUM_CHANNELS, num_classes=2, input_length=2000):
|
| 60 |
+
"""
|
| 61 |
+
Args:
|
| 62 |
+
num_channels: Number of EEG channels (default 19 for standard 10-20)
|
| 63 |
+
num_classes: Number of output classes (default 2: IED vs Non-IED)
|
| 64 |
+
input_length: Number of time samples (default 2000 = 4 seconds at 500 Hz)
|
| 65 |
+
"""
|
| 66 |
+
super(Simple1DCNN, self).__init__()
|
| 67 |
+
|
| 68 |
+
self.num_channels = num_channels
|
| 69 |
+
self.input_length = input_length
|
| 70 |
+
|
| 71 |
+
self.features = nn.Sequential(
|
| 72 |
+
# Block 1
|
| 73 |
+
nn.Conv1d(in_channels=num_channels, out_channels=32, kernel_size=5, stride=1, padding=2),
|
| 74 |
+
nn.BatchNorm1d(32),
|
| 75 |
+
nn.ReLU(),
|
| 76 |
+
nn.MaxPool1d(kernel_size=2, stride=2),
|
| 77 |
+
|
| 78 |
+
# Block 2
|
| 79 |
+
nn.Conv1d(in_channels=32, out_channels=64, kernel_size=5, stride=1, padding=2),
|
| 80 |
+
nn.BatchNorm1d(64),
|
| 81 |
+
nn.ReLU(),
|
| 82 |
+
nn.MaxPool1d(kernel_size=2, stride=2),
|
| 83 |
+
|
| 84 |
+
# Block 3
|
| 85 |
+
nn.Conv1d(in_channels=64, out_channels=128, kernel_size=3, stride=1, padding=1),
|
| 86 |
+
nn.BatchNorm1d(128),
|
| 87 |
+
nn.ReLU(),
|
| 88 |
+
nn.MaxPool1d(kernel_size=2, stride=2),
|
| 89 |
+
|
| 90 |
+
# Block 4
|
| 91 |
+
nn.Conv1d(in_channels=128, out_channels=256, kernel_size=3, stride=1, padding=1),
|
| 92 |
+
nn.BatchNorm1d(256),
|
| 93 |
+
nn.ReLU(),
|
| 94 |
+
nn.MaxPool1d(kernel_size=2, stride=2),
|
| 95 |
+
)
|
| 96 |
+
|
| 97 |
+
# Calculate size after convolutions
|
| 98 |
+
# Input: 2000
|
| 99 |
+
# Pool 1: 1000
|
| 100 |
+
# Pool 2: 500
|
| 101 |
+
# Pool 3: 250
|
| 102 |
+
# Pool 4: 125
|
| 103 |
+
self.flatten_size = 256 * (input_length // 16) # 16 = 2^4 (4 pooling layers)
|
| 104 |
+
|
| 105 |
+
self.classifier = nn.Sequential(
|
| 106 |
+
nn.Linear(self.flatten_size, 512),
|
| 107 |
+
nn.ReLU(),
|
| 108 |
+
nn.Dropout(0.5),
|
| 109 |
+
nn.Linear(512, num_classes)
|
| 110 |
+
)
|
| 111 |
+
|
| 112 |
+
def forward(self, x):
|
| 113 |
+
x = self.features(x)
|
| 114 |
+
x = x.view(x.size(0), -1)
|
| 115 |
+
x = self.classifier(x)
|
| 116 |
+
return x
|
| 117 |
+
|
| 118 |
+
|
| 119 |
+
# ============================================================================
|
| 120 |
+
# Advanced Model Architectures for Better IED Detection
|
| 121 |
+
# ============================================================================
|
| 122 |
+
|
| 123 |
+
class SqueezeExcitation1D(nn.Module):
|
| 124 |
+
"""Squeeze-and-Excitation block for channel attention."""
|
| 125 |
+
|
| 126 |
+
def __init__(self, channels, reduction=16):
|
| 127 |
+
super().__init__()
|
| 128 |
+
self.avg_pool = nn.AdaptiveAvgPool1d(1)
|
| 129 |
+
self.fc = nn.Sequential(
|
| 130 |
+
nn.Linear(channels, channels // reduction, bias=False),
|
| 131 |
+
nn.ReLU(inplace=True),
|
| 132 |
+
nn.Linear(channels // reduction, channels, bias=False),
|
| 133 |
+
nn.Sigmoid()
|
| 134 |
+
)
|
| 135 |
+
|
| 136 |
+
def forward(self, x):
|
| 137 |
+
b, c, _ = x.size()
|
| 138 |
+
y = self.avg_pool(x).view(b, c)
|
| 139 |
+
y = self.fc(y).view(b, c, 1)
|
| 140 |
+
return x * y.expand_as(x)
|
| 141 |
+
|
| 142 |
+
|
| 143 |
+
class ResidualBlock1D(nn.Module):
|
| 144 |
+
"""Residual block with optional squeeze-excitation."""
|
| 145 |
+
|
| 146 |
+
def __init__(self, in_channels, out_channels, kernel_size=3, stride=1,
|
| 147 |
+
downsample=None, use_se=True):
|
| 148 |
+
super().__init__()
|
| 149 |
+
padding = kernel_size // 2
|
| 150 |
+
|
| 151 |
+
self.conv1 = nn.Conv1d(in_channels, out_channels, kernel_size,
|
| 152 |
+
stride=stride, padding=padding, bias=False)
|
| 153 |
+
self.bn1 = nn.BatchNorm1d(out_channels)
|
| 154 |
+
self.relu = nn.ReLU(inplace=True)
|
| 155 |
+
self.conv2 = nn.Conv1d(out_channels, out_channels, kernel_size,
|
| 156 |
+
stride=1, padding=padding, bias=False)
|
| 157 |
+
self.bn2 = nn.BatchNorm1d(out_channels)
|
| 158 |
+
|
| 159 |
+
self.downsample = downsample
|
| 160 |
+
self.se = SqueezeExcitation1D(out_channels) if use_se else nn.Identity()
|
| 161 |
+
|
| 162 |
+
def forward(self, x):
|
| 163 |
+
identity = x
|
| 164 |
+
|
| 165 |
+
out = self.conv1(x)
|
| 166 |
+
out = self.bn1(out)
|
| 167 |
+
out = self.relu(out)
|
| 168 |
+
|
| 169 |
+
out = self.conv2(out)
|
| 170 |
+
out = self.bn2(out)
|
| 171 |
+
out = self.se(out)
|
| 172 |
+
|
| 173 |
+
if self.downsample is not None:
|
| 174 |
+
identity = self.downsample(x)
|
| 175 |
+
|
| 176 |
+
out += identity
|
| 177 |
+
out = self.relu(out)
|
| 178 |
+
return out
|
| 179 |
+
|
| 180 |
+
|
| 181 |
+
class TemporalAttention(nn.Module):
|
| 182 |
+
"""Self-attention mechanism for temporal patterns."""
|
| 183 |
+
|
| 184 |
+
def __init__(self, channels, num_heads=4):
|
| 185 |
+
super().__init__()
|
| 186 |
+
self.num_heads = num_heads
|
| 187 |
+
self.head_dim = channels // num_heads
|
| 188 |
+
self.scale = self.head_dim ** -0.5
|
| 189 |
+
|
| 190 |
+
self.qkv = nn.Linear(channels, channels * 3, bias=False)
|
| 191 |
+
self.proj = nn.Linear(channels, channels)
|
| 192 |
+
self.norm = nn.LayerNorm(channels)
|
| 193 |
+
|
| 194 |
+
def forward(self, x):
|
| 195 |
+
# x: [B, C, T] -> [B, T, C]
|
| 196 |
+
x = x.permute(0, 2, 1)
|
| 197 |
+
B, T, C = x.shape
|
| 198 |
+
|
| 199 |
+
# Residual connection
|
| 200 |
+
residual = x
|
| 201 |
+
x = self.norm(x)
|
| 202 |
+
|
| 203 |
+
# Multi-head attention
|
| 204 |
+
qkv = self.qkv(x).reshape(B, T, 3, self.num_heads, self.head_dim)
|
| 205 |
+
qkv = qkv.permute(2, 0, 3, 1, 4) # [3, B, heads, T, head_dim]
|
| 206 |
+
q, k, v = qkv[0], qkv[1], qkv[2]
|
| 207 |
+
|
| 208 |
+
attn = (q @ k.transpose(-2, -1)) * self.scale
|
| 209 |
+
attn = attn.softmax(dim=-1)
|
| 210 |
+
|
| 211 |
+
x = (attn @ v).transpose(1, 2).reshape(B, T, C)
|
| 212 |
+
x = self.proj(x)
|
| 213 |
+
x = x + residual
|
| 214 |
+
|
| 215 |
+
# Back to [B, C, T]
|
| 216 |
+
return x.permute(0, 2, 1)
|
| 217 |
+
|
| 218 |
+
|
| 219 |
+
class MultiScaleConv(nn.Module):
|
| 220 |
+
"""Multi-scale convolution for capturing different frequency patterns."""
|
| 221 |
+
|
| 222 |
+
def __init__(self, in_channels, out_channels):
|
| 223 |
+
super().__init__()
|
| 224 |
+
self.branch1 = nn.Sequential(
|
| 225 |
+
nn.Conv1d(in_channels, out_channels // 4, kernel_size=3, padding=1, bias=False),
|
| 226 |
+
nn.BatchNorm1d(out_channels // 4),
|
| 227 |
+
nn.ReLU(inplace=True)
|
| 228 |
+
)
|
| 229 |
+
self.branch2 = nn.Sequential(
|
| 230 |
+
nn.Conv1d(in_channels, out_channels // 4, kernel_size=5, padding=2, bias=False),
|
| 231 |
+
nn.BatchNorm1d(out_channels // 4),
|
| 232 |
+
nn.ReLU(inplace=True)
|
| 233 |
+
)
|
| 234 |
+
self.branch3 = nn.Sequential(
|
| 235 |
+
nn.Conv1d(in_channels, out_channels // 4, kernel_size=7, padding=3, bias=False),
|
| 236 |
+
nn.BatchNorm1d(out_channels // 4),
|
| 237 |
+
nn.ReLU(inplace=True)
|
| 238 |
+
)
|
| 239 |
+
self.branch4 = nn.Sequential(
|
| 240 |
+
nn.Conv1d(in_channels, out_channels // 4, kernel_size=11, padding=5, bias=False),
|
| 241 |
+
nn.BatchNorm1d(out_channels // 4),
|
| 242 |
+
nn.ReLU(inplace=True)
|
| 243 |
+
)
|
| 244 |
+
|
| 245 |
+
def forward(self, x):
|
| 246 |
+
return torch.cat([
|
| 247 |
+
self.branch1(x),
|
| 248 |
+
self.branch2(x),
|
| 249 |
+
self.branch3(x),
|
| 250 |
+
self.branch4(x)
|
| 251 |
+
], dim=1)
|
| 252 |
+
|
| 253 |
+
|
| 254 |
+
class EEGResNetAttention(nn.Module):
|
| 255 |
+
"""
|
| 256 |
+
Advanced EEG IED Detector with ResNet backbone and Attention.
|
| 257 |
+
|
| 258 |
+
Features:
|
| 259 |
+
- Multi-scale initial convolution for capturing different EEG frequencies
|
| 260 |
+
- Residual blocks with Squeeze-and-Excitation attention
|
| 261 |
+
- Temporal self-attention for capturing long-range dependencies
|
| 262 |
+
- Global average pooling for robust feature aggregation
|
| 263 |
+
"""
|
| 264 |
+
|
| 265 |
+
def __init__(self, num_channels=NUM_CHANNELS, num_classes=2, input_length=2000):
|
| 266 |
+
super().__init__()
|
| 267 |
+
|
| 268 |
+
self.num_channels = num_channels
|
| 269 |
+
self.input_length = input_length
|
| 270 |
+
|
| 271 |
+
# Multi-scale initial convolution
|
| 272 |
+
self.stem = MultiScaleConv(num_channels, 64)
|
| 273 |
+
self.stem_pool = nn.MaxPool1d(kernel_size=2, stride=2)
|
| 274 |
+
|
| 275 |
+
# Residual blocks
|
| 276 |
+
self.layer1 = self._make_layer(64, 64, num_blocks=2, stride=1)
|
| 277 |
+
self.layer2 = self._make_layer(64, 128, num_blocks=2, stride=2)
|
| 278 |
+
self.layer3 = self._make_layer(128, 256, num_blocks=2, stride=2)
|
| 279 |
+
self.layer4 = self._make_layer(256, 512, num_blocks=2, stride=2)
|
| 280 |
+
|
| 281 |
+
# Temporal attention
|
| 282 |
+
self.temporal_attn = TemporalAttention(512, num_heads=8)
|
| 283 |
+
|
| 284 |
+
# Global pooling and classifier
|
| 285 |
+
self.global_pool = nn.AdaptiveAvgPool1d(1)
|
| 286 |
+
self.dropout = nn.Dropout(0.5)
|
| 287 |
+
self.fc = nn.Linear(512, num_classes)
|
| 288 |
+
|
| 289 |
+
# Initialize weights
|
| 290 |
+
self._init_weights()
|
| 291 |
+
|
| 292 |
+
def _make_layer(self, in_channels, out_channels, num_blocks, stride):
|
| 293 |
+
downsample = None
|
| 294 |
+
if stride != 1 or in_channels != out_channels:
|
| 295 |
+
downsample = nn.Sequential(
|
| 296 |
+
nn.Conv1d(in_channels, out_channels, kernel_size=1, stride=stride, bias=False),
|
| 297 |
+
nn.BatchNorm1d(out_channels)
|
| 298 |
+
)
|
| 299 |
+
|
| 300 |
+
layers = [ResidualBlock1D(in_channels, out_channels, stride=stride, downsample=downsample)]
|
| 301 |
+
for _ in range(1, num_blocks):
|
| 302 |
+
layers.append(ResidualBlock1D(out_channels, out_channels))
|
| 303 |
+
|
| 304 |
+
return nn.Sequential(*layers)
|
| 305 |
+
|
| 306 |
+
def _init_weights(self):
|
| 307 |
+
for m in self.modules():
|
| 308 |
+
if isinstance(m, nn.Conv1d):
|
| 309 |
+
nn.init.kaiming_normal_(m.weight, mode='fan_out', nonlinearity='relu')
|
| 310 |
+
elif isinstance(m, nn.BatchNorm1d):
|
| 311 |
+
nn.init.constant_(m.weight, 1)
|
| 312 |
+
nn.init.constant_(m.bias, 0)
|
| 313 |
+
elif isinstance(m, nn.Linear):
|
| 314 |
+
nn.init.normal_(m.weight, 0, 0.01)
|
| 315 |
+
if m.bias is not None:
|
| 316 |
+
nn.init.constant_(m.bias, 0)
|
| 317 |
+
|
| 318 |
+
def forward(self, x):
|
| 319 |
+
# Multi-scale stem
|
| 320 |
+
x = self.stem(x)
|
| 321 |
+
x = self.stem_pool(x)
|
| 322 |
+
|
| 323 |
+
# Residual blocks
|
| 324 |
+
x = self.layer1(x)
|
| 325 |
+
x = self.layer2(x)
|
| 326 |
+
x = self.layer3(x)
|
| 327 |
+
x = self.layer4(x)
|
| 328 |
+
|
| 329 |
+
# Temporal attention
|
| 330 |
+
x = self.temporal_attn(x)
|
| 331 |
+
|
| 332 |
+
# Global pooling and classification
|
| 333 |
+
x = self.global_pool(x).squeeze(-1)
|
| 334 |
+
x = self.dropout(x)
|
| 335 |
+
x = self.fc(x)
|
| 336 |
+
|
| 337 |
+
return x
|
| 338 |
+
|
| 339 |
+
|
| 340 |
+
class EEGTransformer(nn.Module):
|
| 341 |
+
"""
|
| 342 |
+
Transformer-based EEG IED Detector.
|
| 343 |
+
|
| 344 |
+
Uses a CNN backbone for initial feature extraction followed by
|
| 345 |
+
Transformer encoder layers for capturing complex temporal patterns.
|
| 346 |
+
"""
|
| 347 |
+
|
| 348 |
+
def __init__(self, num_channels=NUM_CHANNELS, num_classes=2, input_length=2000,
|
| 349 |
+
d_model=256, nhead=8, num_layers=4, dim_feedforward=512):
|
| 350 |
+
super().__init__()
|
| 351 |
+
|
| 352 |
+
self.num_channels = num_channels
|
| 353 |
+
self.input_length = input_length
|
| 354 |
+
|
| 355 |
+
# CNN backbone for initial feature extraction
|
| 356 |
+
self.conv_backbone = nn.Sequential(
|
| 357 |
+
nn.Conv1d(num_channels, 64, kernel_size=7, stride=2, padding=3, bias=False),
|
| 358 |
+
nn.BatchNorm1d(64),
|
| 359 |
+
nn.ReLU(inplace=True),
|
| 360 |
+
nn.MaxPool1d(kernel_size=3, stride=2, padding=1),
|
| 361 |
+
|
| 362 |
+
nn.Conv1d(64, 128, kernel_size=5, stride=2, padding=2, bias=False),
|
| 363 |
+
nn.BatchNorm1d(128),
|
| 364 |
+
nn.ReLU(inplace=True),
|
| 365 |
+
|
| 366 |
+
nn.Conv1d(128, d_model, kernel_size=3, stride=2, padding=1, bias=False),
|
| 367 |
+
nn.BatchNorm1d(d_model),
|
| 368 |
+
nn.ReLU(inplace=True),
|
| 369 |
+
)
|
| 370 |
+
|
| 371 |
+
# Calculate sequence length after CNN
|
| 372 |
+
# 2000 -> 1000 -> 500 -> 250 -> 125
|
| 373 |
+
self.seq_len = input_length // 16
|
| 374 |
+
|
| 375 |
+
# Positional encoding
|
| 376 |
+
self.pos_encoding = self._create_positional_encoding(self.seq_len, d_model)
|
| 377 |
+
|
| 378 |
+
# Transformer encoder
|
| 379 |
+
encoder_layer = nn.TransformerEncoderLayer(
|
| 380 |
+
d_model=d_model,
|
| 381 |
+
nhead=nhead,
|
| 382 |
+
dim_feedforward=dim_feedforward,
|
| 383 |
+
dropout=0.1,
|
| 384 |
+
activation='gelu',
|
| 385 |
+
batch_first=True
|
| 386 |
+
)
|
| 387 |
+
self.transformer = nn.TransformerEncoder(encoder_layer, num_layers=num_layers)
|
| 388 |
+
|
| 389 |
+
# Classification head
|
| 390 |
+
self.global_pool = nn.AdaptiveAvgPool1d(1)
|
| 391 |
+
self.classifier = nn.Sequential(
|
| 392 |
+
nn.Linear(d_model, d_model // 2),
|
| 393 |
+
nn.GELU(),
|
| 394 |
+
nn.Dropout(0.3),
|
| 395 |
+
nn.Linear(d_model // 2, num_classes)
|
| 396 |
+
)
|
| 397 |
+
|
| 398 |
+
def _create_positional_encoding(self, seq_len, d_model):
|
| 399 |
+
pe = torch.zeros(seq_len, d_model)
|
| 400 |
+
position = torch.arange(0, seq_len, dtype=torch.float).unsqueeze(1)
|
| 401 |
+
div_term = torch.exp(torch.arange(0, d_model, 2).float() * (-math.log(10000.0) / d_model))
|
| 402 |
+
pe[:, 0::2] = torch.sin(position * div_term)
|
| 403 |
+
pe[:, 1::2] = torch.cos(position * div_term)
|
| 404 |
+
return nn.Parameter(pe.unsqueeze(0), requires_grad=False)
|
| 405 |
+
|
| 406 |
+
def forward(self, x):
|
| 407 |
+
# CNN backbone: [B, C, T] -> [B, d_model, T']
|
| 408 |
+
x = self.conv_backbone(x)
|
| 409 |
+
|
| 410 |
+
# Reshape for transformer: [B, d_model, T'] -> [B, T', d_model]
|
| 411 |
+
x = x.permute(0, 2, 1)
|
| 412 |
+
|
| 413 |
+
# Add positional encoding
|
| 414 |
+
x = x + self.pos_encoding[:, :x.size(1), :]
|
| 415 |
+
|
| 416 |
+
# Transformer encoder
|
| 417 |
+
x = self.transformer(x)
|
| 418 |
+
|
| 419 |
+
# Back to [B, d_model, T'] for pooling
|
| 420 |
+
x = x.permute(0, 2, 1)
|
| 421 |
+
|
| 422 |
+
# Global pooling and classification
|
| 423 |
+
x = self.global_pool(x).squeeze(-1)
|
| 424 |
+
x = self.classifier(x)
|
| 425 |
+
|
| 426 |
+
return x
|
| 427 |
+
|
| 428 |
+
|
| 429 |
+
class EEGInceptionNet(nn.Module):
|
| 430 |
+
"""
|
| 431 |
+
Inception-style network for EEG IED detection.
|
| 432 |
+
|
| 433 |
+
Uses parallel convolutions with different kernel sizes to capture
|
| 434 |
+
multi-scale temporal patterns characteristic of IEDs.
|
| 435 |
+
"""
|
| 436 |
+
|
| 437 |
+
def __init__(self, num_channels=NUM_CHANNELS, num_classes=2, input_length=2000):
|
| 438 |
+
super().__init__()
|
| 439 |
+
|
| 440 |
+
self.num_channels = num_channels
|
| 441 |
+
self.input_length = input_length
|
| 442 |
+
|
| 443 |
+
# Initial convolution
|
| 444 |
+
self.conv1 = nn.Sequential(
|
| 445 |
+
nn.Conv1d(num_channels, 32, kernel_size=7, stride=2, padding=3, bias=False),
|
| 446 |
+
nn.BatchNorm1d(32),
|
| 447 |
+
nn.ReLU(inplace=True),
|
| 448 |
+
nn.MaxPool1d(kernel_size=3, stride=2, padding=1)
|
| 449 |
+
)
|
| 450 |
+
|
| 451 |
+
# Inception blocks
|
| 452 |
+
self.inception1 = InceptionBlock(32, 64)
|
| 453 |
+
self.inception2 = InceptionBlock(64, 128)
|
| 454 |
+
self.inception3 = InceptionBlock(128, 256)
|
| 455 |
+
|
| 456 |
+
# Pooling between inception blocks
|
| 457 |
+
self.pool = nn.MaxPool1d(kernel_size=2, stride=2)
|
| 458 |
+
|
| 459 |
+
# Channel attention
|
| 460 |
+
self.channel_attn = SqueezeExcitation1D(256)
|
| 461 |
+
|
| 462 |
+
# Global pooling and classifier
|
| 463 |
+
self.global_pool = nn.AdaptiveAvgPool1d(1)
|
| 464 |
+
self.dropout = nn.Dropout(0.5)
|
| 465 |
+
self.fc = nn.Linear(256, num_classes)
|
| 466 |
+
|
| 467 |
+
def forward(self, x):
|
| 468 |
+
x = self.conv1(x)
|
| 469 |
+
|
| 470 |
+
x = self.inception1(x)
|
| 471 |
+
x = self.pool(x)
|
| 472 |
+
|
| 473 |
+
x = self.inception2(x)
|
| 474 |
+
x = self.pool(x)
|
| 475 |
+
|
| 476 |
+
x = self.inception3(x)
|
| 477 |
+
x = self.channel_attn(x)
|
| 478 |
+
|
| 479 |
+
x = self.global_pool(x).squeeze(-1)
|
| 480 |
+
x = self.dropout(x)
|
| 481 |
+
x = self.fc(x)
|
| 482 |
+
|
| 483 |
+
return x
|
| 484 |
+
|
| 485 |
+
|
| 486 |
+
class EEGResNetTransformer(nn.Module):
|
| 487 |
+
"""Hybrid model: multi-scale ResNet backbone + Transformer encoder.
|
| 488 |
+
|
| 489 |
+
Rationale:
|
| 490 |
+
- CNN/ResNet extracts local, frequency-like patterns efficiently.
|
| 491 |
+
- Transformer models longer-range temporal dependencies after downsampling.
|
| 492 |
+
- Outputs raw logits suitable for nn.CrossEntropyLoss.
|
| 493 |
+
"""
|
| 494 |
+
|
| 495 |
+
def __init__(
|
| 496 |
+
self,
|
| 497 |
+
num_channels=NUM_CHANNELS,
|
| 498 |
+
num_classes=2,
|
| 499 |
+
input_length=2000,
|
| 500 |
+
d_model=512,
|
| 501 |
+
nhead=8,
|
| 502 |
+
num_layers=4,
|
| 503 |
+
dim_feedforward=1024,
|
| 504 |
+
dropout=0.2,
|
| 505 |
+
):
|
| 506 |
+
super().__init__()
|
| 507 |
+
|
| 508 |
+
self.num_channels = num_channels
|
| 509 |
+
self.input_length = input_length
|
| 510 |
+
|
| 511 |
+
# ResNet-ish backbone (same stem idea as EEGResNetAttention)
|
| 512 |
+
self.stem = MultiScaleConv(num_channels, 64)
|
| 513 |
+
self.stem_pool = nn.MaxPool1d(kernel_size=2, stride=2)
|
| 514 |
+
|
| 515 |
+
self.layer1 = self._make_layer(64, 64, num_blocks=2, stride=1)
|
| 516 |
+
self.layer2 = self._make_layer(64, 128, num_blocks=2, stride=2)
|
| 517 |
+
self.layer3 = self._make_layer(128, 256, num_blocks=2, stride=2)
|
| 518 |
+
self.layer4 = self._make_layer(256, d_model, num_blocks=2, stride=2)
|
| 519 |
+
|
| 520 |
+
# Transformer encoder over time (after downsampling)
|
| 521 |
+
encoder_layer = nn.TransformerEncoderLayer(
|
| 522 |
+
d_model=d_model,
|
| 523 |
+
nhead=nhead,
|
| 524 |
+
dim_feedforward=dim_feedforward,
|
| 525 |
+
dropout=dropout,
|
| 526 |
+
activation='gelu',
|
| 527 |
+
batch_first=True,
|
| 528 |
+
norm_first=True,
|
| 529 |
+
)
|
| 530 |
+
self.transformer = nn.TransformerEncoder(encoder_layer, num_layers=num_layers)
|
| 531 |
+
|
| 532 |
+
# Learnable positional embeddings (sequence length depends on input length)
|
| 533 |
+
# Downsampling: stem_pool(/2) + layer2(/2) + layer3(/2) + layer4(/2) => /16 total
|
| 534 |
+
self.seq_len = max(1, input_length // 16)
|
| 535 |
+
self.pos_embed = nn.Parameter(torch.zeros(1, self.seq_len, d_model))
|
| 536 |
+
|
| 537 |
+
self.pre_head_norm = nn.LayerNorm(d_model)
|
| 538 |
+
self.dropout = nn.Dropout(dropout)
|
| 539 |
+
self.head = nn.Linear(d_model, num_classes)
|
| 540 |
+
|
| 541 |
+
self._init_weights()
|
| 542 |
+
|
| 543 |
+
def _make_layer(self, in_channels, out_channels, num_blocks, stride):
|
| 544 |
+
downsample = None
|
| 545 |
+
if stride != 1 or in_channels != out_channels:
|
| 546 |
+
downsample = nn.Sequential(
|
| 547 |
+
nn.Conv1d(in_channels, out_channels, kernel_size=1, stride=stride, bias=False),
|
| 548 |
+
nn.BatchNorm1d(out_channels),
|
| 549 |
+
)
|
| 550 |
+
|
| 551 |
+
layers = [ResidualBlock1D(in_channels, out_channels, stride=stride, downsample=downsample)]
|
| 552 |
+
for _ in range(1, num_blocks):
|
| 553 |
+
layers.append(ResidualBlock1D(out_channels, out_channels))
|
| 554 |
+
return nn.Sequential(*layers)
|
| 555 |
+
|
| 556 |
+
def _init_weights(self):
|
| 557 |
+
for m in self.modules():
|
| 558 |
+
if isinstance(m, nn.Conv1d):
|
| 559 |
+
nn.init.kaiming_normal_(m.weight, mode='fan_out', nonlinearity='relu')
|
| 560 |
+
elif isinstance(m, nn.BatchNorm1d):
|
| 561 |
+
nn.init.constant_(m.weight, 1)
|
| 562 |
+
nn.init.constant_(m.bias, 0)
|
| 563 |
+
elif isinstance(m, nn.Linear):
|
| 564 |
+
nn.init.trunc_normal_(m.weight, std=0.02)
|
| 565 |
+
if m.bias is not None:
|
| 566 |
+
nn.init.constant_(m.bias, 0)
|
| 567 |
+
|
| 568 |
+
nn.init.trunc_normal_(self.pos_embed, std=0.02)
|
| 569 |
+
|
| 570 |
+
def forward(self, x):
|
| 571 |
+
# x: [B, C, T]
|
| 572 |
+
x = self.stem(x)
|
| 573 |
+
x = self.stem_pool(x)
|
| 574 |
+
x = self.layer1(x)
|
| 575 |
+
x = self.layer2(x)
|
| 576 |
+
x = self.layer3(x)
|
| 577 |
+
x = self.layer4(x) # [B, d_model, T']
|
| 578 |
+
|
| 579 |
+
# [B, d_model, T'] -> [B, T', d_model]
|
| 580 |
+
x = x.permute(0, 2, 1)
|
| 581 |
+
|
| 582 |
+
# Handle dynamic T' (e.g. if input_length differs)
|
| 583 |
+
if x.size(1) != self.pos_embed.size(1):
|
| 584 |
+
# Simple interpolate positional embeddings along time
|
| 585 |
+
pos = self.pos_embed.transpose(1, 2) # [1, d_model, seq]
|
| 586 |
+
pos = F.interpolate(pos, size=x.size(1), mode='linear', align_corners=False)
|
| 587 |
+
pos = pos.transpose(1, 2)
|
| 588 |
+
else:
|
| 589 |
+
pos = self.pos_embed
|
| 590 |
+
|
| 591 |
+
x = x + pos
|
| 592 |
+
x = self.transformer(x)
|
| 593 |
+
|
| 594 |
+
# Mean pool over time
|
| 595 |
+
x = x.mean(dim=1)
|
| 596 |
+
x = self.pre_head_norm(x)
|
| 597 |
+
x = self.dropout(x)
|
| 598 |
+
return self.head(x)
|
| 599 |
+
|
| 600 |
+
|
| 601 |
+
class InceptionBlock(nn.Module):
|
| 602 |
+
"""Inception block with multiple parallel convolutions."""
|
| 603 |
+
|
| 604 |
+
def __init__(self, in_channels, out_channels):
|
| 605 |
+
super().__init__()
|
| 606 |
+
branch_channels = out_channels // 4
|
| 607 |
+
|
| 608 |
+
# 1x1 conv
|
| 609 |
+
self.branch1 = nn.Sequential(
|
| 610 |
+
nn.Conv1d(in_channels, branch_channels, kernel_size=1, bias=False),
|
| 611 |
+
nn.BatchNorm1d(branch_channels),
|
| 612 |
+
nn.ReLU(inplace=True)
|
| 613 |
+
)
|
| 614 |
+
|
| 615 |
+
# 1x1 -> 3x3 conv
|
| 616 |
+
self.branch2 = nn.Sequential(
|
| 617 |
+
nn.Conv1d(in_channels, branch_channels, kernel_size=1, bias=False),
|
| 618 |
+
nn.BatchNorm1d(branch_channels),
|
| 619 |
+
nn.ReLU(inplace=True),
|
| 620 |
+
nn.Conv1d(branch_channels, branch_channels, kernel_size=3, padding=1, bias=False),
|
| 621 |
+
nn.BatchNorm1d(branch_channels),
|
| 622 |
+
nn.ReLU(inplace=True)
|
| 623 |
+
)
|
| 624 |
+
|
| 625 |
+
# 1x1 -> 5x5 conv
|
| 626 |
+
self.branch3 = nn.Sequential(
|
| 627 |
+
nn.Conv1d(in_channels, branch_channels, kernel_size=1, bias=False),
|
| 628 |
+
nn.BatchNorm1d(branch_channels),
|
| 629 |
+
nn.ReLU(inplace=True),
|
| 630 |
+
nn.Conv1d(branch_channels, branch_channels, kernel_size=5, padding=2, bias=False),
|
| 631 |
+
nn.BatchNorm1d(branch_channels),
|
| 632 |
+
nn.ReLU(inplace=True)
|
| 633 |
+
)
|
| 634 |
+
|
| 635 |
+
# MaxPool -> 1x1 conv
|
| 636 |
+
self.branch4 = nn.Sequential(
|
| 637 |
+
nn.MaxPool1d(kernel_size=3, stride=1, padding=1),
|
| 638 |
+
nn.Conv1d(in_channels, branch_channels, kernel_size=1, bias=False),
|
| 639 |
+
nn.BatchNorm1d(branch_channels),
|
| 640 |
+
nn.ReLU(inplace=True)
|
| 641 |
+
)
|
| 642 |
+
|
| 643 |
+
def forward(self, x):
|
| 644 |
+
return torch.cat([
|
| 645 |
+
self.branch1(x),
|
| 646 |
+
self.branch2(x),
|
| 647 |
+
self.branch3(x),
|
| 648 |
+
self.branch4(x)
|
| 649 |
+
], dim=1)
|
| 650 |
+
|
| 651 |
+
|
| 652 |
+
# Model factory function
|
| 653 |
+
def get_model(model_name='resnet_attention', num_channels=NUM_CHANNELS, num_classes=2, input_length=2000):
|
| 654 |
+
"""
|
| 655 |
+
Factory function to get model by name.
|
| 656 |
+
|
| 657 |
+
Args:
|
| 658 |
+
model_name: One of 'simple_cnn', 'resnet_attention', 'transformer', 'inception', 'resnet_transformer'
|
| 659 |
+
num_channels: Number of input channels
|
| 660 |
+
num_classes: Number of output classes
|
| 661 |
+
input_length: Input sequence length
|
| 662 |
+
|
| 663 |
+
Returns:
|
| 664 |
+
Model instance
|
| 665 |
+
"""
|
| 666 |
+
models = {
|
| 667 |
+
'simple_cnn': Simple1DCNN,
|
| 668 |
+
'resnet_attention': EEGResNetAttention,
|
| 669 |
+
'transformer': EEGTransformer,
|
| 670 |
+
'inception': EEGInceptionNet,
|
| 671 |
+
'resnet_transformer': EEGResNetTransformer,
|
| 672 |
+
}
|
| 673 |
+
|
| 674 |
+
if model_name not in models:
|
| 675 |
+
raise ValueError(f"Unknown model: {model_name}. Choose from {list(models.keys())}")
|
| 676 |
+
|
| 677 |
+
return models[model_name](num_channels=num_channels, num_classes=num_classes, input_length=input_length)
|
eegannotationtool/senua_resnet_attention/preprocessing.py
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Exact four-second preprocessing used by the released SenuaLab models."""
|
| 2 |
+
|
| 3 |
+
from __future__ import annotations
|
| 4 |
+
|
| 5 |
+
import math
|
| 6 |
+
|
| 7 |
+
import numpy as np
|
| 8 |
+
from scipy.signal import butter, resample, resample_poly, sosfiltfilt
|
| 9 |
+
|
| 10 |
+
|
| 11 |
+
NUM_CHANNELS = 19
|
| 12 |
+
INTERNATIONAL_10_20_CHANNELS = [
|
| 13 |
+
"Fp1", "Fp2", "F3", "F4", "C3", "C4", "P3", "P4", "O1", "O2",
|
| 14 |
+
"F7", "F8", "T3", "T4", "T5", "T6", "Fz", "Cz", "Pz",
|
| 15 |
+
]
|
| 16 |
+
DATASET_29_CHANNEL_ORDER = INTERNATIONAL_10_20_CHANNELS
|
| 17 |
+
|
| 18 |
+
|
| 19 |
+
def normalize_sample(sample):
|
| 20 |
+
x = np.asarray(sample, dtype=np.float32)
|
| 21 |
+
mean = x.mean()
|
| 22 |
+
std = x.std()
|
| 23 |
+
return np.clip((x - mean) / max(float(std), 1e-7), -8.0, 8.0).astype(np.float32)
|
| 24 |
+
|
| 25 |
+
|
| 26 |
+
def preprocess_batch(
|
| 27 |
+
batch,
|
| 28 |
+
*,
|
| 29 |
+
source_sfreq=None,
|
| 30 |
+
target_sfreq=250,
|
| 31 |
+
target_samples=1000,
|
| 32 |
+
channel_names=None,
|
| 33 |
+
):
|
| 34 |
+
"""Filter, average-reference, resample, and globally normalize `[B,C,T]`."""
|
| 35 |
+
|
| 36 |
+
del channel_names # IED Finder has already arranged the declared channel order.
|
| 37 |
+
x = np.asarray(batch, dtype=np.float32)
|
| 38 |
+
if x.ndim == 2:
|
| 39 |
+
x = x[None]
|
| 40 |
+
if x.ndim != 3 or x.shape[1] != NUM_CHANNELS:
|
| 41 |
+
raise ValueError(f"Expected [batch,19,time], received {x.shape}")
|
| 42 |
+
sfreq = float(source_sfreq or target_sfreq)
|
| 43 |
+
if sfreq <= 90.0:
|
| 44 |
+
raise ValueError(f"Sampling rate must be above the 45 Hz cutoff; received {sfreq}")
|
| 45 |
+
sos = butter(4, [1.0, 45.0], btype="bandpass", fs=sfreq, output="sos")
|
| 46 |
+
x = sosfiltfilt(sos, x, axis=-1).astype(np.float32, copy=False)
|
| 47 |
+
x -= x.mean(axis=1, keepdims=True)
|
| 48 |
+
if not math.isclose(sfreq, float(target_sfreq)):
|
| 49 |
+
up = int(target_sfreq)
|
| 50 |
+
down = int(round(sfreq))
|
| 51 |
+
common = math.gcd(up, down)
|
| 52 |
+
x = resample_poly(x, up // common, down // common, axis=-1).astype(np.float32)
|
| 53 |
+
if x.shape[-1] != int(target_samples):
|
| 54 |
+
x = resample(x, int(target_samples), axis=-1).astype(np.float32)
|
| 55 |
+
mean = x.mean(axis=(1, 2), keepdims=True)
|
| 56 |
+
std = x.std(axis=(1, 2), keepdims=True)
|
| 57 |
+
return np.clip((x - mean) / np.maximum(std, 1e-7), -8.0, 8.0).astype(np.float32)
|
| 58 |
+
|
ensembles/ensemble.json
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"models": [
|
| 3 |
+
"iednet_lite",
|
| 4 |
+
"resnet_attention"
|
| 5 |
+
],
|
| 6 |
+
"test_metrics": {
|
| 7 |
+
"accuracy": 0.93014329580348,
|
| 8 |
+
"auprc": 0.7211157678062519,
|
| 9 |
+
"auroc": 0.8995878534153646,
|
| 10 |
+
"balanced_accuracy": 0.8135185936874053,
|
| 11 |
+
"f1": 0.6784452296819788,
|
| 12 |
+
"false_positives_per_minute": 0.5483592400690847,
|
| 13 |
+
"fn": 146,
|
| 14 |
+
"fp": 127,
|
| 15 |
+
"n": 3908,
|
| 16 |
+
"precision": 0.6939759036144578,
|
| 17 |
+
"prevalence": 0.11105424769703173,
|
| 18 |
+
"sensitivity": 0.663594470046083,
|
| 19 |
+
"specificity": 0.9634427173287277,
|
| 20 |
+
"threshold": 0.51321724,
|
| 21 |
+
"tn": 3347,
|
| 22 |
+
"tp": 288
|
| 23 |
+
},
|
| 24 |
+
"threshold": 0.51321724
|
| 25 |
+
}
|
ensembles/ensemble_4model.json
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"aggregation": "equal_probability_mean",
|
| 3 |
+
"models": [
|
| 4 |
+
"iednet_lite",
|
| 5 |
+
"resnet_attention",
|
| 6 |
+
"eegpt",
|
| 7 |
+
"eegdino_medium"
|
| 8 |
+
],
|
| 9 |
+
"selection_score_validation_auprc_plus_025_f1": 0.8335915544686525,
|
| 10 |
+
"test_metrics": {
|
| 11 |
+
"accuracy": 0.9483111566018424,
|
| 12 |
+
"auprc": 0.7888269120699914,
|
| 13 |
+
"auroc": 0.9200864088462283,
|
| 14 |
+
"balanced_accuracy": 0.8600306689058151,
|
| 15 |
+
"f1": 0.7623529411764706,
|
| 16 |
+
"false_positives_per_minute": 0.39723661485319517,
|
| 17 |
+
"fn": 110,
|
| 18 |
+
"fp": 92,
|
| 19 |
+
"n": 3908,
|
| 20 |
+
"precision": 0.7788461538461539,
|
| 21 |
+
"prevalence": 0.11105424769703173,
|
| 22 |
+
"sensitivity": 0.7465437788018433,
|
| 23 |
+
"specificity": 0.973517559009787,
|
| 24 |
+
"threshold": 0.5273836475,
|
| 25 |
+
"tn": 3382,
|
| 26 |
+
"tp": 324
|
| 27 |
+
},
|
| 28 |
+
"threshold": 0.5273836475,
|
| 29 |
+
"validation_metrics": {
|
| 30 |
+
"accuracy": 0.9512570548999487,
|
| 31 |
+
"auprc": 0.6725428653300758,
|
| 32 |
+
"auroc": 0.8953611234795638,
|
| 33 |
+
"balanced_accuracy": 0.7657309048759975,
|
| 34 |
+
"f1": 0.6441947565543071,
|
| 35 |
+
"false_positives_per_minute": 0.19262981574539362,
|
| 36 |
+
"fn": 144,
|
| 37 |
+
"fp": 46,
|
| 38 |
+
"n": 3898,
|
| 39 |
+
"precision": 0.7889908256880734,
|
| 40 |
+
"prevalence": 0.08106721395587481,
|
| 41 |
+
"sensitivity": 0.5443037974683544,
|
| 42 |
+
"specificity": 0.9871580122836404,
|
| 43 |
+
"threshold": 0.5273836475,
|
| 44 |
+
"tn": 3536,
|
| 45 |
+
"tp": 172
|
| 46 |
+
},
|
| 47 |
+
"weights": [
|
| 48 |
+
0.25,
|
| 49 |
+
0.25,
|
| 50 |
+
0.25,
|
| 51 |
+
0.25
|
| 52 |
+
]
|
| 53 |
+
}
|
ensembles/ensemble_eegdino.json
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"aggregation": "equal_probability_mean",
|
| 3 |
+
"models": [
|
| 4 |
+
"iednet_lite",
|
| 5 |
+
"resnet_attention",
|
| 6 |
+
"eegdino_medium"
|
| 7 |
+
],
|
| 8 |
+
"selection_score_validation_auprc_plus_025_f1": 0.8454267628396437,
|
| 9 |
+
"test_metrics": {
|
| 10 |
+
"accuracy": 0.9437052200614124,
|
| 11 |
+
"auprc": 0.7895565635533457,
|
| 12 |
+
"auroc": 0.9261677928734589,
|
| 13 |
+
"balanced_accuracy": 0.8463503736778014,
|
| 14 |
+
"f1": 0.7399527186761229,
|
| 15 |
+
"false_positives_per_minute": 0.4274611398963731,
|
| 16 |
+
"fn": 121,
|
| 17 |
+
"fp": 99,
|
| 18 |
+
"n": 3908,
|
| 19 |
+
"precision": 0.7597087378640777,
|
| 20 |
+
"prevalence": 0.11105424769703173,
|
| 21 |
+
"sensitivity": 0.7211981566820277,
|
| 22 |
+
"specificity": 0.9715025906735751,
|
| 23 |
+
"threshold": 0.5274549233333333,
|
| 24 |
+
"tn": 3375,
|
| 25 |
+
"tp": 313
|
| 26 |
+
},
|
| 27 |
+
"threshold": 0.5274549233333333,
|
| 28 |
+
"validation_metrics": {
|
| 29 |
+
"accuracy": 0.9510005130836326,
|
| 30 |
+
"auprc": 0.6860339734658296,
|
| 31 |
+
"auroc": 0.9016363462884041,
|
| 32 |
+
"balanced_accuracy": 0.7598205514209585,
|
| 33 |
+
"f1": 0.6375711574952562,
|
| 34 |
+
"false_positives_per_minute": 0.18006700167504186,
|
| 35 |
+
"fn": 148,
|
| 36 |
+
"fp": 43,
|
| 37 |
+
"n": 3898,
|
| 38 |
+
"precision": 0.7962085308056872,
|
| 39 |
+
"prevalence": 0.08106721395587481,
|
| 40 |
+
"sensitivity": 0.5316455696202531,
|
| 41 |
+
"specificity": 0.9879955332216639,
|
| 42 |
+
"threshold": 0.5274549233333333,
|
| 43 |
+
"tn": 3539,
|
| 44 |
+
"tp": 168
|
| 45 |
+
},
|
| 46 |
+
"weights": [
|
| 47 |
+
0.3333333333333333,
|
| 48 |
+
0.3333333333333333,
|
| 49 |
+
0.3333333333333333
|
| 50 |
+
]
|
| 51 |
+
}
|
ensembles/ensemble_eegpt.json
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"aggregation": "equal_probability_mean",
|
| 3 |
+
"models": [
|
| 4 |
+
"iednet_lite",
|
| 5 |
+
"resnet_attention",
|
| 6 |
+
"eegpt"
|
| 7 |
+
],
|
| 8 |
+
"test_metrics": {
|
| 9 |
+
"accuracy": 0.936028659160696,
|
| 10 |
+
"auprc": 0.7451331821970812,
|
| 11 |
+
"auroc": 0.9004447787249058,
|
| 12 |
+
"balanced_accuracy": 0.8248940781950977,
|
| 13 |
+
"f1": 0.7030878859857482,
|
| 14 |
+
"false_positives_per_minute": 0.4835924006908463,
|
| 15 |
+
"fn": 138,
|
| 16 |
+
"fp": 112,
|
| 17 |
+
"n": 3908,
|
| 18 |
+
"precision": 0.7254901960784313,
|
| 19 |
+
"prevalence": 0.11105424769703173,
|
| 20 |
+
"sensitivity": 0.6820276497695853,
|
| 21 |
+
"specificity": 0.9677605066206102,
|
| 22 |
+
"threshold": 0.5290762755,
|
| 23 |
+
"tn": 3362,
|
| 24 |
+
"tp": 296
|
| 25 |
+
},
|
| 26 |
+
"threshold": 0.5290762755,
|
| 27 |
+
"validation_metrics": {
|
| 28 |
+
"accuracy": 0.9499743458183684,
|
| 29 |
+
"auprc": 0.6261332028552472,
|
| 30 |
+
"auroc": 0.8396483118829026,
|
| 31 |
+
"balanced_accuracy": 0.7578195124709342,
|
| 32 |
+
"f1": 0.6313799621928167,
|
| 33 |
+
"false_positives_per_minute": 0.19262981574539362,
|
| 34 |
+
"fn": 149,
|
| 35 |
+
"fp": 46,
|
| 36 |
+
"n": 3898,
|
| 37 |
+
"precision": 0.784037558685446,
|
| 38 |
+
"prevalence": 0.08106721395587481,
|
| 39 |
+
"sensitivity": 0.5284810126582279,
|
| 40 |
+
"specificity": 0.9871580122836404,
|
| 41 |
+
"threshold": 0.5290762755,
|
| 42 |
+
"tn": 3536,
|
| 43 |
+
"tp": 167
|
| 44 |
+
},
|
| 45 |
+
"weights": [
|
| 46 |
+
0.3333333333333333,
|
| 47 |
+
0.3333333333333333,
|
| 48 |
+
0.3333333333333333
|
| 49 |
+
]
|
| 50 |
+
}
|
models/eegdino_medium/config.json
ADDED
|
@@ -0,0 +1,130 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"binary_labels": [
|
| 3 |
+
"Non-IED",
|
| 4 |
+
"IED"
|
| 5 |
+
],
|
| 6 |
+
"channels": [
|
| 7 |
+
"Fp1",
|
| 8 |
+
"Fp2",
|
| 9 |
+
"F3",
|
| 10 |
+
"F4",
|
| 11 |
+
"C3",
|
| 12 |
+
"C4",
|
| 13 |
+
"P3",
|
| 14 |
+
"P4",
|
| 15 |
+
"O1",
|
| 16 |
+
"O2",
|
| 17 |
+
"F7",
|
| 18 |
+
"F8",
|
| 19 |
+
"T3",
|
| 20 |
+
"T4",
|
| 21 |
+
"T5",
|
| 22 |
+
"T6",
|
| 23 |
+
"Fz",
|
| 24 |
+
"Cz",
|
| 25 |
+
"Pz"
|
| 26 |
+
],
|
| 27 |
+
"eegdino_channel_names": [
|
| 28 |
+
"Fp1",
|
| 29 |
+
"Fp2",
|
| 30 |
+
"F3",
|
| 31 |
+
"F4",
|
| 32 |
+
"C3",
|
| 33 |
+
"C4",
|
| 34 |
+
"P3",
|
| 35 |
+
"P4",
|
| 36 |
+
"O1",
|
| 37 |
+
"O2",
|
| 38 |
+
"F7",
|
| 39 |
+
"F8",
|
| 40 |
+
"T3",
|
| 41 |
+
"T4",
|
| 42 |
+
"T5",
|
| 43 |
+
"T6",
|
| 44 |
+
"Fz",
|
| 45 |
+
"Cz",
|
| 46 |
+
"Pz"
|
| 47 |
+
],
|
| 48 |
+
"epoch": 11,
|
| 49 |
+
"format_version": 2,
|
| 50 |
+
"model_name": "eegdino_medium",
|
| 51 |
+
"parameters": 34445250,
|
| 52 |
+
"preprocessing": {
|
| 53 |
+
"bandpass_hz": [
|
| 54 |
+
1.0,
|
| 55 |
+
45.0
|
| 56 |
+
],
|
| 57 |
+
"normalization": "global_window_zscore_clip8",
|
| 58 |
+
"reference": "average"
|
| 59 |
+
},
|
| 60 |
+
"pretrained": {
|
| 61 |
+
"intentionally_not_loaded": [
|
| 62 |
+
"final_layer.* (new downstream binary IED head)"
|
| 63 |
+
],
|
| 64 |
+
"loaded_tensors": 227,
|
| 65 |
+
"loaded_values": 33755456,
|
| 66 |
+
"missing_after_load": [
|
| 67 |
+
"final_layer.token_proj.0.weight",
|
| 68 |
+
"final_layer.token_proj.0.bias",
|
| 69 |
+
"final_layer.time_proj.0.weight",
|
| 70 |
+
"final_layer.time_proj.0.bias",
|
| 71 |
+
"final_layer.classifier.0.weight",
|
| 72 |
+
"final_layer.classifier.0.bias",
|
| 73 |
+
"final_layer.classifier.3.weight",
|
| 74 |
+
"final_layer.classifier.3.bias",
|
| 75 |
+
"final_layer.classifier.6.weight",
|
| 76 |
+
"final_layer.classifier.6.bias"
|
| 77 |
+
],
|
| 78 |
+
"repo_id": "braindecode/eegdino-medium-pretrained",
|
| 79 |
+
"requested_revision": "191f73eb50d68a184b4cabb623938df914235d3c",
|
| 80 |
+
"resolved_revision": "191f73eb50d68a184b4cabb623938df914235d3c",
|
| 81 |
+
"unexpected_after_load": [],
|
| 82 |
+
"weights_file": "model.safetensors",
|
| 83 |
+
"weights_sha256": "17a73d056afde262d1be0cc21434ca751855691726a0500b3f3c809e2eecdf9e"
|
| 84 |
+
},
|
| 85 |
+
"release_name": "eegdino_medium",
|
| 86 |
+
"sfreq": 200,
|
| 87 |
+
"split_seed": 2026,
|
| 88 |
+
"subtype_labels": [],
|
| 89 |
+
"test_metrics": {
|
| 90 |
+
"accuracy": 0.9398669396110543,
|
| 91 |
+
"auprc": 0.8019567240287226,
|
| 92 |
+
"auroc": 0.9362860777493904,
|
| 93 |
+
"balanced_accuracy": 0.8683870171836076,
|
| 94 |
+
"f1": 0.7414741474147415,
|
| 95 |
+
"false_positives_per_minute": 0.5958549222797928,
|
| 96 |
+
"fn": 97,
|
| 97 |
+
"fp": 138,
|
| 98 |
+
"n": 3908,
|
| 99 |
+
"precision": 0.7094736842105264,
|
| 100 |
+
"prevalence": 0.11105424769703173,
|
| 101 |
+
"sensitivity": 0.7764976958525346,
|
| 102 |
+
"specificity": 0.9602763385146805,
|
| 103 |
+
"threshold": 0.619140625,
|
| 104 |
+
"tn": 3336,
|
| 105 |
+
"tp": 337
|
| 106 |
+
},
|
| 107 |
+
"training_strategy": "partial_last_2",
|
| 108 |
+
"validation_metrics": {
|
| 109 |
+
"accuracy": 0.9461262185736276,
|
| 110 |
+
"auprc": 0.6916968240811774,
|
| 111 |
+
"auroc": 0.8983352946165426,
|
| 112 |
+
"balanced_accuracy": 0.7903503099180855,
|
| 113 |
+
"f1": 0.6452702702702703,
|
| 114 |
+
"false_positives_per_minute": 0.35594639865996647,
|
| 115 |
+
"fn": 125,
|
| 116 |
+
"fp": 85,
|
| 117 |
+
"n": 3898,
|
| 118 |
+
"precision": 0.6920289855072463,
|
| 119 |
+
"prevalence": 0.08106721395587481,
|
| 120 |
+
"sensitivity": 0.6044303797468354,
|
| 121 |
+
"specificity": 0.9762702400893356,
|
| 122 |
+
"threshold": 0.619140625,
|
| 123 |
+
"tn": 3497,
|
| 124 |
+
"tp": 191
|
| 125 |
+
},
|
| 126 |
+
"validation_threshold_f1": 0.619140625,
|
| 127 |
+
"validation_threshold_sensitivity80": 0.056121826171875,
|
| 128 |
+
"window_samples": 800,
|
| 129 |
+
"window_seconds": 4.0
|
| 130 |
+
}
|
models/eegdino_medium/model.safetensors
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:72320fedeed09b66508afaa79ce0ca114273673f6ce36adccedce87c8ce0ea1d
|
| 3 |
+
size 137804848
|
models/eegpt/config.json
ADDED
|
@@ -0,0 +1,122 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"binary_labels": [
|
| 3 |
+
"Non-IED",
|
| 4 |
+
"IED"
|
| 5 |
+
],
|
| 6 |
+
"channels": [
|
| 7 |
+
"Fp1",
|
| 8 |
+
"Fp2",
|
| 9 |
+
"F3",
|
| 10 |
+
"F4",
|
| 11 |
+
"C3",
|
| 12 |
+
"C4",
|
| 13 |
+
"P3",
|
| 14 |
+
"P4",
|
| 15 |
+
"O1",
|
| 16 |
+
"O2",
|
| 17 |
+
"F7",
|
| 18 |
+
"F8",
|
| 19 |
+
"T3",
|
| 20 |
+
"T4",
|
| 21 |
+
"T5",
|
| 22 |
+
"T6",
|
| 23 |
+
"Fz",
|
| 24 |
+
"Cz",
|
| 25 |
+
"Pz"
|
| 26 |
+
],
|
| 27 |
+
"eegpt_channel_names": [
|
| 28 |
+
"Fp1",
|
| 29 |
+
"Fp2",
|
| 30 |
+
"F3",
|
| 31 |
+
"F4",
|
| 32 |
+
"C3",
|
| 33 |
+
"C4",
|
| 34 |
+
"P3",
|
| 35 |
+
"P4",
|
| 36 |
+
"O1",
|
| 37 |
+
"O2",
|
| 38 |
+
"F7",
|
| 39 |
+
"F8",
|
| 40 |
+
"T7",
|
| 41 |
+
"T8",
|
| 42 |
+
"P7",
|
| 43 |
+
"P8",
|
| 44 |
+
"Fz",
|
| 45 |
+
"Cz",
|
| 46 |
+
"Pz"
|
| 47 |
+
],
|
| 48 |
+
"epoch": 28,
|
| 49 |
+
"format_version": 2,
|
| 50 |
+
"model_name": "eegpt",
|
| 51 |
+
"parameters": 25589507,
|
| 52 |
+
"preprocessing": {
|
| 53 |
+
"bandpass_hz": [
|
| 54 |
+
1.0,
|
| 55 |
+
45.0
|
| 56 |
+
],
|
| 57 |
+
"normalization": "global_window_zscore_clip8",
|
| 58 |
+
"reference": "average"
|
| 59 |
+
},
|
| 60 |
+
"pretrained": {
|
| 61 |
+
"intentionally_not_loaded": [
|
| 62 |
+
"chans_id (downstream 19-channel identity buffer)",
|
| 63 |
+
"final_layer.* (new binary IED classification head)"
|
| 64 |
+
],
|
| 65 |
+
"loaded_tensors": 102,
|
| 66 |
+
"loaded_values": 25287168,
|
| 67 |
+
"missing_after_load": [
|
| 68 |
+
"chans_id"
|
| 69 |
+
],
|
| 70 |
+
"repo_id": "braindecode/eegpt-pretrained",
|
| 71 |
+
"resolved_revision": "e41cb3ae2ce4fd9eb736862292c91f8128d15618",
|
| 72 |
+
"unexpected_after_load": [],
|
| 73 |
+
"weights_file": "model.safetensors",
|
| 74 |
+
"weights_sha256": "fb34c20609983324679b9534f9d17a2289a232d0276dd2b8b7d5b088f876f621"
|
| 75 |
+
},
|
| 76 |
+
"release_name": "eegpt",
|
| 77 |
+
"sfreq": 250,
|
| 78 |
+
"split_seed": 2026,
|
| 79 |
+
"subtype_labels": [],
|
| 80 |
+
"test_metrics": {
|
| 81 |
+
"accuracy": 0.9201637666325486,
|
| 82 |
+
"auprc": 0.65324809994628,
|
| 83 |
+
"auroc": 0.8640698911466086,
|
| 84 |
+
"balanced_accuracy": 0.767579570688379,
|
| 85 |
+
"f1": 0.6138613861386139,
|
| 86 |
+
"false_positives_per_minute": 0.5440414507772021,
|
| 87 |
+
"fn": 186,
|
| 88 |
+
"fp": 126,
|
| 89 |
+
"n": 3908,
|
| 90 |
+
"precision": 0.6631016042780749,
|
| 91 |
+
"prevalence": 0.11105424769703173,
|
| 92 |
+
"sensitivity": 0.5714285714285714,
|
| 93 |
+
"specificity": 0.9637305699481865,
|
| 94 |
+
"threshold": 0.8916015625,
|
| 95 |
+
"tn": 3348,
|
| 96 |
+
"tp": 248
|
| 97 |
+
},
|
| 98 |
+
"trainable_probe_parameters": 302339,
|
| 99 |
+
"training_strategy": "pretrained_encoder_frozen_ied_temporal_head",
|
| 100 |
+
"validation_metrics": {
|
| 101 |
+
"accuracy": 0.9420215495125706,
|
| 102 |
+
"auprc": 0.5628674715868012,
|
| 103 |
+
"auroc": 0.8119094063849486,
|
| 104 |
+
"balanced_accuracy": 0.7289665627716642,
|
| 105 |
+
"f1": 0.5703422053231939,
|
| 106 |
+
"false_positives_per_minute": 0.25125628140703515,
|
| 107 |
+
"fn": 166,
|
| 108 |
+
"fp": 60,
|
| 109 |
+
"n": 3898,
|
| 110 |
+
"precision": 0.7142857142857143,
|
| 111 |
+
"prevalence": 0.08106721395587481,
|
| 112 |
+
"sensitivity": 0.47468354430379744,
|
| 113 |
+
"specificity": 0.983249581239531,
|
| 114 |
+
"threshold": 0.8916015625,
|
| 115 |
+
"tn": 3522,
|
| 116 |
+
"tp": 150
|
| 117 |
+
},
|
| 118 |
+
"validation_threshold_f1": 0.8916015625,
|
| 119 |
+
"validation_threshold_sensitivity80": 0.007965087890625,
|
| 120 |
+
"window_samples": 1000,
|
| 121 |
+
"window_seconds": 4.0
|
| 122 |
+
}
|
models/eegpt/model.safetensors
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:e91757b80c98dd7325ea1068bff9dfe72bac577195f5b25b2570ce4d9f357226
|
| 3 |
+
size 102373540
|
models/eegpt_linear_probe/config.json
ADDED
|
@@ -0,0 +1,122 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"binary_labels": [
|
| 3 |
+
"Non-IED",
|
| 4 |
+
"IED"
|
| 5 |
+
],
|
| 6 |
+
"channels": [
|
| 7 |
+
"Fp1",
|
| 8 |
+
"Fp2",
|
| 9 |
+
"F3",
|
| 10 |
+
"F4",
|
| 11 |
+
"C3",
|
| 12 |
+
"C4",
|
| 13 |
+
"P3",
|
| 14 |
+
"P4",
|
| 15 |
+
"O1",
|
| 16 |
+
"O2",
|
| 17 |
+
"F7",
|
| 18 |
+
"F8",
|
| 19 |
+
"T3",
|
| 20 |
+
"T4",
|
| 21 |
+
"T5",
|
| 22 |
+
"T6",
|
| 23 |
+
"Fz",
|
| 24 |
+
"Cz",
|
| 25 |
+
"Pz"
|
| 26 |
+
],
|
| 27 |
+
"eegpt_channel_names": [
|
| 28 |
+
"Fp1",
|
| 29 |
+
"Fp2",
|
| 30 |
+
"F3",
|
| 31 |
+
"F4",
|
| 32 |
+
"C3",
|
| 33 |
+
"C4",
|
| 34 |
+
"P3",
|
| 35 |
+
"P4",
|
| 36 |
+
"O1",
|
| 37 |
+
"O2",
|
| 38 |
+
"F7",
|
| 39 |
+
"F8",
|
| 40 |
+
"T7",
|
| 41 |
+
"T8",
|
| 42 |
+
"P7",
|
| 43 |
+
"P8",
|
| 44 |
+
"Fz",
|
| 45 |
+
"Cz",
|
| 46 |
+
"Pz"
|
| 47 |
+
],
|
| 48 |
+
"epoch": 19,
|
| 49 |
+
"format_version": 2,
|
| 50 |
+
"model_name": "eegpt_linear",
|
| 51 |
+
"parameters": 25320946,
|
| 52 |
+
"preprocessing": {
|
| 53 |
+
"bandpass_hz": [
|
| 54 |
+
1.0,
|
| 55 |
+
45.0
|
| 56 |
+
],
|
| 57 |
+
"normalization": "global_window_zscore_clip8",
|
| 58 |
+
"reference": "average"
|
| 59 |
+
},
|
| 60 |
+
"pretrained": {
|
| 61 |
+
"intentionally_not_loaded": [
|
| 62 |
+
"chans_id (downstream 19-channel identity buffer)",
|
| 63 |
+
"final_layer.* (new binary IED classification head)"
|
| 64 |
+
],
|
| 65 |
+
"loaded_tensors": 102,
|
| 66 |
+
"loaded_values": 25287168,
|
| 67 |
+
"missing_after_load": [
|
| 68 |
+
"chans_id"
|
| 69 |
+
],
|
| 70 |
+
"repo_id": "braindecode/eegpt-pretrained",
|
| 71 |
+
"resolved_revision": "e41cb3ae2ce4fd9eb736862292c91f8128d15618",
|
| 72 |
+
"unexpected_after_load": [],
|
| 73 |
+
"weights_file": "model.safetensors",
|
| 74 |
+
"weights_sha256": "fb34c20609983324679b9534f9d17a2289a232d0276dd2b8b7d5b088f876f621"
|
| 75 |
+
},
|
| 76 |
+
"release_name": "eegpt_linear_probe",
|
| 77 |
+
"sfreq": 250,
|
| 78 |
+
"split_seed": 2026,
|
| 79 |
+
"subtype_labels": [],
|
| 80 |
+
"test_metrics": {
|
| 81 |
+
"accuracy": 0.7131525076765609,
|
| 82 |
+
"auprc": 0.3722106087915067,
|
| 83 |
+
"auroc": 0.7519221126525154,
|
| 84 |
+
"balanced_accuracy": 0.6702979871540794,
|
| 85 |
+
"f1": 0.32265861027190335,
|
| 86 |
+
"false_positives_per_minute": 4.119170984455959,
|
| 87 |
+
"fn": 167,
|
| 88 |
+
"fp": 954,
|
| 89 |
+
"n": 3908,
|
| 90 |
+
"precision": 0.21867321867321868,
|
| 91 |
+
"prevalence": 0.11105424769703173,
|
| 92 |
+
"sensitivity": 0.6152073732718893,
|
| 93 |
+
"specificity": 0.7253886010362695,
|
| 94 |
+
"threshold": 0.40234375,
|
| 95 |
+
"tn": 2520,
|
| 96 |
+
"tp": 267
|
| 97 |
+
},
|
| 98 |
+
"trainable_probe_parameters": 33778,
|
| 99 |
+
"training_strategy": "pretrained_encoder_frozen_linear_probe",
|
| 100 |
+
"validation_metrics": {
|
| 101 |
+
"accuracy": 0.8496664956387892,
|
| 102 |
+
"auprc": 0.23998456130581225,
|
| 103 |
+
"auroc": 0.7217963940659697,
|
| 104 |
+
"balanced_accuracy": 0.6715018482002135,
|
| 105 |
+
"f1": 0.3310502283105023,
|
| 106 |
+
"false_positives_per_minute": 1.7378559463986598,
|
| 107 |
+
"fn": 171,
|
| 108 |
+
"fp": 415,
|
| 109 |
+
"n": 3898,
|
| 110 |
+
"precision": 0.25892857142857145,
|
| 111 |
+
"prevalence": 0.08106721395587481,
|
| 112 |
+
"sensitivity": 0.4588607594936709,
|
| 113 |
+
"specificity": 0.884142936906756,
|
| 114 |
+
"threshold": 0.40234375,
|
| 115 |
+
"tn": 3167,
|
| 116 |
+
"tp": 145
|
| 117 |
+
},
|
| 118 |
+
"validation_threshold_f1": 0.40234375,
|
| 119 |
+
"validation_threshold_sensitivity80": 0.049224853515625,
|
| 120 |
+
"window_samples": 1000,
|
| 121 |
+
"window_seconds": 4.0
|
| 122 |
+
}
|
models/eegpt_linear_probe/model.safetensors
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:24ec011a90388079df98322244b96e076eeebf6b0671fe987a7d0d5cf66f9579
|
| 3 |
+
size 101295352
|
models/iednet_lite/config.json
ADDED
|
@@ -0,0 +1,89 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"binary_labels": [
|
| 3 |
+
"Non-IED",
|
| 4 |
+
"IED"
|
| 5 |
+
],
|
| 6 |
+
"channels": [
|
| 7 |
+
"Fp1",
|
| 8 |
+
"Fp2",
|
| 9 |
+
"F3",
|
| 10 |
+
"F4",
|
| 11 |
+
"C3",
|
| 12 |
+
"C4",
|
| 13 |
+
"P3",
|
| 14 |
+
"P4",
|
| 15 |
+
"O1",
|
| 16 |
+
"O2",
|
| 17 |
+
"F7",
|
| 18 |
+
"F8",
|
| 19 |
+
"T3",
|
| 20 |
+
"T4",
|
| 21 |
+
"T5",
|
| 22 |
+
"T6",
|
| 23 |
+
"Fz",
|
| 24 |
+
"Cz",
|
| 25 |
+
"Pz"
|
| 26 |
+
],
|
| 27 |
+
"epoch": 13,
|
| 28 |
+
"format_version": 1,
|
| 29 |
+
"model_name": "iednet_lite",
|
| 30 |
+
"parameters": 245260,
|
| 31 |
+
"preprocessing": {
|
| 32 |
+
"bandpass_hz": [
|
| 33 |
+
1.0,
|
| 34 |
+
45.0
|
| 35 |
+
],
|
| 36 |
+
"normalization": "global_window_zscore_clip8",
|
| 37 |
+
"reference": "average"
|
| 38 |
+
},
|
| 39 |
+
"release_name": "iednet_lite",
|
| 40 |
+
"sfreq": 250,
|
| 41 |
+
"split_seed": 2026,
|
| 42 |
+
"subtype_labels": [
|
| 43 |
+
"Centro-Parietal-IED",
|
| 44 |
+
"Frontal-IED",
|
| 45 |
+
"Generalized-IED",
|
| 46 |
+
"Occipital-IED",
|
| 47 |
+
"Temporal-IED"
|
| 48 |
+
],
|
| 49 |
+
"test_metrics": {
|
| 50 |
+
"accuracy": 0.9201637666325486,
|
| 51 |
+
"auprc": 0.6847588452968619,
|
| 52 |
+
"auroc": 0.8793068455863041,
|
| 53 |
+
"balanced_accuracy": 0.7937914036861053,
|
| 54 |
+
"f1": 0.6372093023255814,
|
| 55 |
+
"false_positives_per_minute": 0.6563039723661486,
|
| 56 |
+
"fn": 160,
|
| 57 |
+
"fp": 152,
|
| 58 |
+
"n": 3908,
|
| 59 |
+
"precision": 0.6431924882629108,
|
| 60 |
+
"prevalence": 0.11105424769703173,
|
| 61 |
+
"sensitivity": 0.631336405529954,
|
| 62 |
+
"specificity": 0.9562464018422567,
|
| 63 |
+
"threshold": 0.6004377007484436,
|
| 64 |
+
"tn": 3322,
|
| 65 |
+
"tp": 274
|
| 66 |
+
},
|
| 67 |
+
"validation_metrics": {
|
| 68 |
+
"accuracy": 0.9502308876346844,
|
| 69 |
+
"auprc": 0.6136477369244443,
|
| 70 |
+
"auroc": 0.8519717080479754,
|
| 71 |
+
"balanced_accuracy": 0.752188332661903,
|
| 72 |
+
"f1": 0.6269230769230769,
|
| 73 |
+
"false_positives_per_minute": 0.17169179229480735,
|
| 74 |
+
"fn": 153,
|
| 75 |
+
"fp": 41,
|
| 76 |
+
"n": 3898,
|
| 77 |
+
"precision": 0.7990196078431373,
|
| 78 |
+
"prevalence": 0.08106721395587481,
|
| 79 |
+
"sensitivity": 0.5158227848101266,
|
| 80 |
+
"specificity": 0.9885538805136795,
|
| 81 |
+
"threshold": 0.6004377007484436,
|
| 82 |
+
"tn": 3541,
|
| 83 |
+
"tp": 163
|
| 84 |
+
},
|
| 85 |
+
"validation_threshold_f1": 0.6004377007484436,
|
| 86 |
+
"validation_threshold_sensitivity80": 0.017428677529096603,
|
| 87 |
+
"window_samples": 1000,
|
| 88 |
+
"window_seconds": 4.0
|
| 89 |
+
}
|
models/iednet_lite/model.safetensors
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:7c5dc3c8e40050b69a07a7cf6a1ce7bbd2b12347f1b50d7feaf1fdfdd108b082
|
| 3 |
+
size 1014056
|
models/resnet_attention/config.json
ADDED
|
@@ -0,0 +1,89 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"binary_labels": [
|
| 3 |
+
"Non-IED",
|
| 4 |
+
"IED"
|
| 5 |
+
],
|
| 6 |
+
"channels": [
|
| 7 |
+
"Fp1",
|
| 8 |
+
"Fp2",
|
| 9 |
+
"F3",
|
| 10 |
+
"F4",
|
| 11 |
+
"C3",
|
| 12 |
+
"C4",
|
| 13 |
+
"P3",
|
| 14 |
+
"P4",
|
| 15 |
+
"O1",
|
| 16 |
+
"O2",
|
| 17 |
+
"F7",
|
| 18 |
+
"F8",
|
| 19 |
+
"T3",
|
| 20 |
+
"T4",
|
| 21 |
+
"T5",
|
| 22 |
+
"T6",
|
| 23 |
+
"Fz",
|
| 24 |
+
"Cz",
|
| 25 |
+
"Pz"
|
| 26 |
+
],
|
| 27 |
+
"epoch": 11,
|
| 28 |
+
"format_version": 1,
|
| 29 |
+
"model_name": "resnet_attention",
|
| 30 |
+
"parameters": 4989538,
|
| 31 |
+
"preprocessing": {
|
| 32 |
+
"bandpass_hz": [
|
| 33 |
+
1.0,
|
| 34 |
+
45.0
|
| 35 |
+
],
|
| 36 |
+
"normalization": "global_window_zscore_clip8",
|
| 37 |
+
"reference": "average"
|
| 38 |
+
},
|
| 39 |
+
"release_name": "resnet_attention",
|
| 40 |
+
"sfreq": 250,
|
| 41 |
+
"split_seed": 2026,
|
| 42 |
+
"subtype_labels": [
|
| 43 |
+
"Centro-Parietal-IED",
|
| 44 |
+
"Frontal-IED",
|
| 45 |
+
"Generalized-IED",
|
| 46 |
+
"Occipital-IED",
|
| 47 |
+
"Temporal-IED"
|
| 48 |
+
],
|
| 49 |
+
"test_metrics": {
|
| 50 |
+
"accuracy": 0.932446264073695,
|
| 51 |
+
"auprc": 0.7018171290865336,
|
| 52 |
+
"auroc": 0.8998458595650639,
|
| 53 |
+
"balanced_accuracy": 0.7906183923232226,
|
| 54 |
+
"f1": 0.6666666666666666,
|
| 55 |
+
"false_positives_per_minute": 0.4058721934369603,
|
| 56 |
+
"fn": 170,
|
| 57 |
+
"fp": 94,
|
| 58 |
+
"n": 3908,
|
| 59 |
+
"precision": 0.7374301675977654,
|
| 60 |
+
"prevalence": 0.11105424769703173,
|
| 61 |
+
"sensitivity": 0.6082949308755761,
|
| 62 |
+
"specificity": 0.9729418537708693,
|
| 63 |
+
"threshold": 0.6944227814674377,
|
| 64 |
+
"tn": 3380,
|
| 65 |
+
"tp": 264
|
| 66 |
+
},
|
| 67 |
+
"validation_metrics": {
|
| 68 |
+
"accuracy": 0.9461262185736276,
|
| 69 |
+
"auprc": 0.5585556572790095,
|
| 70 |
+
"auroc": 0.7953542324845042,
|
| 71 |
+
"balanced_accuracy": 0.741298793545788,
|
| 72 |
+
"f1": 0.5992366412213741,
|
| 73 |
+
"false_positives_per_minute": 0.21356783919597988,
|
| 74 |
+
"fn": 159,
|
| 75 |
+
"fp": 51,
|
| 76 |
+
"n": 3898,
|
| 77 |
+
"precision": 0.7548076923076923,
|
| 78 |
+
"prevalence": 0.08106721395587481,
|
| 79 |
+
"sensitivity": 0.49683544303797467,
|
| 80 |
+
"specificity": 0.9857621440536013,
|
| 81 |
+
"threshold": 0.6944227814674377,
|
| 82 |
+
"tn": 3531,
|
| 83 |
+
"tp": 157
|
| 84 |
+
},
|
| 85 |
+
"validation_threshold_f1": 0.6944227814674377,
|
| 86 |
+
"validation_threshold_sensitivity80": 0.007963554933667183,
|
| 87 |
+
"window_samples": 1000,
|
| 88 |
+
"window_seconds": 4.0
|
| 89 |
+
}
|
models/resnet_attention/model.safetensors
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:971fa0d2b38f0cc76ca479931872ae957123aeb51263de59b11098be1531b1e1
|
| 3 |
+
size 20011088
|
pytorch/eegdino_medium/config.json
ADDED
|
@@ -0,0 +1,130 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"binary_labels": [
|
| 3 |
+
"Non-IED",
|
| 4 |
+
"IED"
|
| 5 |
+
],
|
| 6 |
+
"channels": [
|
| 7 |
+
"Fp1",
|
| 8 |
+
"Fp2",
|
| 9 |
+
"F3",
|
| 10 |
+
"F4",
|
| 11 |
+
"C3",
|
| 12 |
+
"C4",
|
| 13 |
+
"P3",
|
| 14 |
+
"P4",
|
| 15 |
+
"O1",
|
| 16 |
+
"O2",
|
| 17 |
+
"F7",
|
| 18 |
+
"F8",
|
| 19 |
+
"T3",
|
| 20 |
+
"T4",
|
| 21 |
+
"T5",
|
| 22 |
+
"T6",
|
| 23 |
+
"Fz",
|
| 24 |
+
"Cz",
|
| 25 |
+
"Pz"
|
| 26 |
+
],
|
| 27 |
+
"eegdino_channel_names": [
|
| 28 |
+
"Fp1",
|
| 29 |
+
"Fp2",
|
| 30 |
+
"F3",
|
| 31 |
+
"F4",
|
| 32 |
+
"C3",
|
| 33 |
+
"C4",
|
| 34 |
+
"P3",
|
| 35 |
+
"P4",
|
| 36 |
+
"O1",
|
| 37 |
+
"O2",
|
| 38 |
+
"F7",
|
| 39 |
+
"F8",
|
| 40 |
+
"T3",
|
| 41 |
+
"T4",
|
| 42 |
+
"T5",
|
| 43 |
+
"T6",
|
| 44 |
+
"Fz",
|
| 45 |
+
"Cz",
|
| 46 |
+
"Pz"
|
| 47 |
+
],
|
| 48 |
+
"epoch": 11,
|
| 49 |
+
"format_version": 2,
|
| 50 |
+
"model_name": "eegdino_medium",
|
| 51 |
+
"parameters": 34445250,
|
| 52 |
+
"preprocessing": {
|
| 53 |
+
"bandpass_hz": [
|
| 54 |
+
1.0,
|
| 55 |
+
45.0
|
| 56 |
+
],
|
| 57 |
+
"normalization": "global_window_zscore_clip8",
|
| 58 |
+
"reference": "average"
|
| 59 |
+
},
|
| 60 |
+
"pretrained": {
|
| 61 |
+
"intentionally_not_loaded": [
|
| 62 |
+
"final_layer.* (new downstream binary IED head)"
|
| 63 |
+
],
|
| 64 |
+
"loaded_tensors": 227,
|
| 65 |
+
"loaded_values": 33755456,
|
| 66 |
+
"missing_after_load": [
|
| 67 |
+
"final_layer.token_proj.0.weight",
|
| 68 |
+
"final_layer.token_proj.0.bias",
|
| 69 |
+
"final_layer.time_proj.0.weight",
|
| 70 |
+
"final_layer.time_proj.0.bias",
|
| 71 |
+
"final_layer.classifier.0.weight",
|
| 72 |
+
"final_layer.classifier.0.bias",
|
| 73 |
+
"final_layer.classifier.3.weight",
|
| 74 |
+
"final_layer.classifier.3.bias",
|
| 75 |
+
"final_layer.classifier.6.weight",
|
| 76 |
+
"final_layer.classifier.6.bias"
|
| 77 |
+
],
|
| 78 |
+
"repo_id": "braindecode/eegdino-medium-pretrained",
|
| 79 |
+
"requested_revision": "191f73eb50d68a184b4cabb623938df914235d3c",
|
| 80 |
+
"resolved_revision": "191f73eb50d68a184b4cabb623938df914235d3c",
|
| 81 |
+
"unexpected_after_load": [],
|
| 82 |
+
"weights_file": "model.safetensors",
|
| 83 |
+
"weights_sha256": "17a73d056afde262d1be0cc21434ca751855691726a0500b3f3c809e2eecdf9e"
|
| 84 |
+
},
|
| 85 |
+
"release_name": "eegdino_medium",
|
| 86 |
+
"sfreq": 200,
|
| 87 |
+
"split_seed": 2026,
|
| 88 |
+
"subtype_labels": [],
|
| 89 |
+
"test_metrics": {
|
| 90 |
+
"accuracy": 0.9398669396110543,
|
| 91 |
+
"auprc": 0.8019567240287226,
|
| 92 |
+
"auroc": 0.9362860777493904,
|
| 93 |
+
"balanced_accuracy": 0.8683870171836076,
|
| 94 |
+
"f1": 0.7414741474147415,
|
| 95 |
+
"false_positives_per_minute": 0.5958549222797928,
|
| 96 |
+
"fn": 97,
|
| 97 |
+
"fp": 138,
|
| 98 |
+
"n": 3908,
|
| 99 |
+
"precision": 0.7094736842105264,
|
| 100 |
+
"prevalence": 0.11105424769703173,
|
| 101 |
+
"sensitivity": 0.7764976958525346,
|
| 102 |
+
"specificity": 0.9602763385146805,
|
| 103 |
+
"threshold": 0.619140625,
|
| 104 |
+
"tn": 3336,
|
| 105 |
+
"tp": 337
|
| 106 |
+
},
|
| 107 |
+
"training_strategy": "partial_last_2",
|
| 108 |
+
"validation_metrics": {
|
| 109 |
+
"accuracy": 0.9461262185736276,
|
| 110 |
+
"auprc": 0.6916968240811774,
|
| 111 |
+
"auroc": 0.8983352946165426,
|
| 112 |
+
"balanced_accuracy": 0.7903503099180855,
|
| 113 |
+
"f1": 0.6452702702702703,
|
| 114 |
+
"false_positives_per_minute": 0.35594639865996647,
|
| 115 |
+
"fn": 125,
|
| 116 |
+
"fp": 85,
|
| 117 |
+
"n": 3898,
|
| 118 |
+
"precision": 0.6920289855072463,
|
| 119 |
+
"prevalence": 0.08106721395587481,
|
| 120 |
+
"sensitivity": 0.6044303797468354,
|
| 121 |
+
"specificity": 0.9762702400893356,
|
| 122 |
+
"threshold": 0.619140625,
|
| 123 |
+
"tn": 3497,
|
| 124 |
+
"tp": 191
|
| 125 |
+
},
|
| 126 |
+
"validation_threshold_f1": 0.619140625,
|
| 127 |
+
"validation_threshold_sensitivity80": 0.056121826171875,
|
| 128 |
+
"window_samples": 800,
|
| 129 |
+
"window_seconds": 4.0
|
| 130 |
+
}
|
pytorch/eegdino_medium/model.pth
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:e80ee1f66edcac1059b6e68c7458d8aa171167fbc9d50495daaafdc20e358491
|
| 3 |
+
size 137840831
|
pytorch/eegpt/config.json
ADDED
|
@@ -0,0 +1,122 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"binary_labels": [
|
| 3 |
+
"Non-IED",
|
| 4 |
+
"IED"
|
| 5 |
+
],
|
| 6 |
+
"channels": [
|
| 7 |
+
"Fp1",
|
| 8 |
+
"Fp2",
|
| 9 |
+
"F3",
|
| 10 |
+
"F4",
|
| 11 |
+
"C3",
|
| 12 |
+
"C4",
|
| 13 |
+
"P3",
|
| 14 |
+
"P4",
|
| 15 |
+
"O1",
|
| 16 |
+
"O2",
|
| 17 |
+
"F7",
|
| 18 |
+
"F8",
|
| 19 |
+
"T3",
|
| 20 |
+
"T4",
|
| 21 |
+
"T5",
|
| 22 |
+
"T6",
|
| 23 |
+
"Fz",
|
| 24 |
+
"Cz",
|
| 25 |
+
"Pz"
|
| 26 |
+
],
|
| 27 |
+
"eegpt_channel_names": [
|
| 28 |
+
"Fp1",
|
| 29 |
+
"Fp2",
|
| 30 |
+
"F3",
|
| 31 |
+
"F4",
|
| 32 |
+
"C3",
|
| 33 |
+
"C4",
|
| 34 |
+
"P3",
|
| 35 |
+
"P4",
|
| 36 |
+
"O1",
|
| 37 |
+
"O2",
|
| 38 |
+
"F7",
|
| 39 |
+
"F8",
|
| 40 |
+
"T7",
|
| 41 |
+
"T8",
|
| 42 |
+
"P7",
|
| 43 |
+
"P8",
|
| 44 |
+
"Fz",
|
| 45 |
+
"Cz",
|
| 46 |
+
"Pz"
|
| 47 |
+
],
|
| 48 |
+
"epoch": 28,
|
| 49 |
+
"format_version": 2,
|
| 50 |
+
"model_name": "eegpt",
|
| 51 |
+
"parameters": 25589507,
|
| 52 |
+
"preprocessing": {
|
| 53 |
+
"bandpass_hz": [
|
| 54 |
+
1.0,
|
| 55 |
+
45.0
|
| 56 |
+
],
|
| 57 |
+
"normalization": "global_window_zscore_clip8",
|
| 58 |
+
"reference": "average"
|
| 59 |
+
},
|
| 60 |
+
"pretrained": {
|
| 61 |
+
"intentionally_not_loaded": [
|
| 62 |
+
"chans_id (downstream 19-channel identity buffer)",
|
| 63 |
+
"final_layer.* (new binary IED classification head)"
|
| 64 |
+
],
|
| 65 |
+
"loaded_tensors": 102,
|
| 66 |
+
"loaded_values": 25287168,
|
| 67 |
+
"missing_after_load": [
|
| 68 |
+
"chans_id"
|
| 69 |
+
],
|
| 70 |
+
"repo_id": "braindecode/eegpt-pretrained",
|
| 71 |
+
"resolved_revision": "e41cb3ae2ce4fd9eb736862292c91f8128d15618",
|
| 72 |
+
"unexpected_after_load": [],
|
| 73 |
+
"weights_file": "model.safetensors",
|
| 74 |
+
"weights_sha256": "fb34c20609983324679b9534f9d17a2289a232d0276dd2b8b7d5b088f876f621"
|
| 75 |
+
},
|
| 76 |
+
"release_name": "eegpt",
|
| 77 |
+
"sfreq": 250,
|
| 78 |
+
"split_seed": 2026,
|
| 79 |
+
"subtype_labels": [],
|
| 80 |
+
"test_metrics": {
|
| 81 |
+
"accuracy": 0.9201637666325486,
|
| 82 |
+
"auprc": 0.65324809994628,
|
| 83 |
+
"auroc": 0.8640698911466086,
|
| 84 |
+
"balanced_accuracy": 0.767579570688379,
|
| 85 |
+
"f1": 0.6138613861386139,
|
| 86 |
+
"false_positives_per_minute": 0.5440414507772021,
|
| 87 |
+
"fn": 186,
|
| 88 |
+
"fp": 126,
|
| 89 |
+
"n": 3908,
|
| 90 |
+
"precision": 0.6631016042780749,
|
| 91 |
+
"prevalence": 0.11105424769703173,
|
| 92 |
+
"sensitivity": 0.5714285714285714,
|
| 93 |
+
"specificity": 0.9637305699481865,
|
| 94 |
+
"threshold": 0.8916015625,
|
| 95 |
+
"tn": 3348,
|
| 96 |
+
"tp": 248
|
| 97 |
+
},
|
| 98 |
+
"trainable_probe_parameters": 302339,
|
| 99 |
+
"training_strategy": "pretrained_encoder_frozen_ied_temporal_head",
|
| 100 |
+
"validation_metrics": {
|
| 101 |
+
"accuracy": 0.9420215495125706,
|
| 102 |
+
"auprc": 0.5628674715868012,
|
| 103 |
+
"auroc": 0.8119094063849486,
|
| 104 |
+
"balanced_accuracy": 0.7289665627716642,
|
| 105 |
+
"f1": 0.5703422053231939,
|
| 106 |
+
"false_positives_per_minute": 0.25125628140703515,
|
| 107 |
+
"fn": 166,
|
| 108 |
+
"fp": 60,
|
| 109 |
+
"n": 3898,
|
| 110 |
+
"precision": 0.7142857142857143,
|
| 111 |
+
"prevalence": 0.08106721395587481,
|
| 112 |
+
"sensitivity": 0.47468354430379744,
|
| 113 |
+
"specificity": 0.983249581239531,
|
| 114 |
+
"threshold": 0.8916015625,
|
| 115 |
+
"tn": 3522,
|
| 116 |
+
"tp": 150
|
| 117 |
+
},
|
| 118 |
+
"validation_threshold_f1": 0.8916015625,
|
| 119 |
+
"validation_threshold_sensitivity80": 0.007965087890625,
|
| 120 |
+
"window_samples": 1000,
|
| 121 |
+
"window_seconds": 4.0
|
| 122 |
+
}
|
pytorch/eegpt/model.pth
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:9a0a55ed656049b4109d0e923ee0d461151a8a793995a24438717efecd2f24d5
|
| 3 |
+
size 102394167
|
pytorch/eegpt_linear_probe/config.json
ADDED
|
@@ -0,0 +1,122 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"binary_labels": [
|
| 3 |
+
"Non-IED",
|
| 4 |
+
"IED"
|
| 5 |
+
],
|
| 6 |
+
"channels": [
|
| 7 |
+
"Fp1",
|
| 8 |
+
"Fp2",
|
| 9 |
+
"F3",
|
| 10 |
+
"F4",
|
| 11 |
+
"C3",
|
| 12 |
+
"C4",
|
| 13 |
+
"P3",
|
| 14 |
+
"P4",
|
| 15 |
+
"O1",
|
| 16 |
+
"O2",
|
| 17 |
+
"F7",
|
| 18 |
+
"F8",
|
| 19 |
+
"T3",
|
| 20 |
+
"T4",
|
| 21 |
+
"T5",
|
| 22 |
+
"T6",
|
| 23 |
+
"Fz",
|
| 24 |
+
"Cz",
|
| 25 |
+
"Pz"
|
| 26 |
+
],
|
| 27 |
+
"eegpt_channel_names": [
|
| 28 |
+
"Fp1",
|
| 29 |
+
"Fp2",
|
| 30 |
+
"F3",
|
| 31 |
+
"F4",
|
| 32 |
+
"C3",
|
| 33 |
+
"C4",
|
| 34 |
+
"P3",
|
| 35 |
+
"P4",
|
| 36 |
+
"O1",
|
| 37 |
+
"O2",
|
| 38 |
+
"F7",
|
| 39 |
+
"F8",
|
| 40 |
+
"T7",
|
| 41 |
+
"T8",
|
| 42 |
+
"P7",
|
| 43 |
+
"P8",
|
| 44 |
+
"Fz",
|
| 45 |
+
"Cz",
|
| 46 |
+
"Pz"
|
| 47 |
+
],
|
| 48 |
+
"epoch": 19,
|
| 49 |
+
"format_version": 2,
|
| 50 |
+
"model_name": "eegpt_linear",
|
| 51 |
+
"parameters": 25320946,
|
| 52 |
+
"preprocessing": {
|
| 53 |
+
"bandpass_hz": [
|
| 54 |
+
1.0,
|
| 55 |
+
45.0
|
| 56 |
+
],
|
| 57 |
+
"normalization": "global_window_zscore_clip8",
|
| 58 |
+
"reference": "average"
|
| 59 |
+
},
|
| 60 |
+
"pretrained": {
|
| 61 |
+
"intentionally_not_loaded": [
|
| 62 |
+
"chans_id (downstream 19-channel identity buffer)",
|
| 63 |
+
"final_layer.* (new binary IED classification head)"
|
| 64 |
+
],
|
| 65 |
+
"loaded_tensors": 102,
|
| 66 |
+
"loaded_values": 25287168,
|
| 67 |
+
"missing_after_load": [
|
| 68 |
+
"chans_id"
|
| 69 |
+
],
|
| 70 |
+
"repo_id": "braindecode/eegpt-pretrained",
|
| 71 |
+
"resolved_revision": "e41cb3ae2ce4fd9eb736862292c91f8128d15618",
|
| 72 |
+
"unexpected_after_load": [],
|
| 73 |
+
"weights_file": "model.safetensors",
|
| 74 |
+
"weights_sha256": "fb34c20609983324679b9534f9d17a2289a232d0276dd2b8b7d5b088f876f621"
|
| 75 |
+
},
|
| 76 |
+
"release_name": "eegpt_linear_probe",
|
| 77 |
+
"sfreq": 250,
|
| 78 |
+
"split_seed": 2026,
|
| 79 |
+
"subtype_labels": [],
|
| 80 |
+
"test_metrics": {
|
| 81 |
+
"accuracy": 0.7131525076765609,
|
| 82 |
+
"auprc": 0.3722106087915067,
|
| 83 |
+
"auroc": 0.7519221126525154,
|
| 84 |
+
"balanced_accuracy": 0.6702979871540794,
|
| 85 |
+
"f1": 0.32265861027190335,
|
| 86 |
+
"false_positives_per_minute": 4.119170984455959,
|
| 87 |
+
"fn": 167,
|
| 88 |
+
"fp": 954,
|
| 89 |
+
"n": 3908,
|
| 90 |
+
"precision": 0.21867321867321868,
|
| 91 |
+
"prevalence": 0.11105424769703173,
|
| 92 |
+
"sensitivity": 0.6152073732718893,
|
| 93 |
+
"specificity": 0.7253886010362695,
|
| 94 |
+
"threshold": 0.40234375,
|
| 95 |
+
"tn": 2520,
|
| 96 |
+
"tp": 267
|
| 97 |
+
},
|
| 98 |
+
"trainable_probe_parameters": 33778,
|
| 99 |
+
"training_strategy": "pretrained_encoder_frozen_linear_probe",
|
| 100 |
+
"validation_metrics": {
|
| 101 |
+
"accuracy": 0.8496664956387892,
|
| 102 |
+
"auprc": 0.23998456130581225,
|
| 103 |
+
"auroc": 0.7217963940659697,
|
| 104 |
+
"balanced_accuracy": 0.6715018482002135,
|
| 105 |
+
"f1": 0.3310502283105023,
|
| 106 |
+
"false_positives_per_minute": 1.7378559463986598,
|
| 107 |
+
"fn": 171,
|
| 108 |
+
"fp": 415,
|
| 109 |
+
"n": 3898,
|
| 110 |
+
"precision": 0.25892857142857145,
|
| 111 |
+
"prevalence": 0.08106721395587481,
|
| 112 |
+
"sensitivity": 0.4588607594936709,
|
| 113 |
+
"specificity": 0.884142936906756,
|
| 114 |
+
"threshold": 0.40234375,
|
| 115 |
+
"tn": 3167,
|
| 116 |
+
"tp": 145
|
| 117 |
+
},
|
| 118 |
+
"validation_threshold_f1": 0.40234375,
|
| 119 |
+
"validation_threshold_sensitivity80": 0.049224853515625,
|
| 120 |
+
"window_samples": 1000,
|
| 121 |
+
"window_seconds": 4.0
|
| 122 |
+
}
|
pytorch/eegpt_linear_probe/model.pth
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:a51fd32505d51d80677ed6504b94d0b8a6718cb1de21a07cd3253ed1e1a3405a
|
| 3 |
+
size 101312967
|
pytorch/iednet_lite/config.json
ADDED
|
@@ -0,0 +1,89 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"binary_labels": [
|
| 3 |
+
"Non-IED",
|
| 4 |
+
"IED"
|
| 5 |
+
],
|
| 6 |
+
"channels": [
|
| 7 |
+
"Fp1",
|
| 8 |
+
"Fp2",
|
| 9 |
+
"F3",
|
| 10 |
+
"F4",
|
| 11 |
+
"C3",
|
| 12 |
+
"C4",
|
| 13 |
+
"P3",
|
| 14 |
+
"P4",
|
| 15 |
+
"O1",
|
| 16 |
+
"O2",
|
| 17 |
+
"F7",
|
| 18 |
+
"F8",
|
| 19 |
+
"T3",
|
| 20 |
+
"T4",
|
| 21 |
+
"T5",
|
| 22 |
+
"T6",
|
| 23 |
+
"Fz",
|
| 24 |
+
"Cz",
|
| 25 |
+
"Pz"
|
| 26 |
+
],
|
| 27 |
+
"epoch": 13,
|
| 28 |
+
"format_version": 1,
|
| 29 |
+
"model_name": "iednet_lite",
|
| 30 |
+
"parameters": 245260,
|
| 31 |
+
"preprocessing": {
|
| 32 |
+
"bandpass_hz": [
|
| 33 |
+
1.0,
|
| 34 |
+
45.0
|
| 35 |
+
],
|
| 36 |
+
"normalization": "global_window_zscore_clip8",
|
| 37 |
+
"reference": "average"
|
| 38 |
+
},
|
| 39 |
+
"release_name": "iednet_lite",
|
| 40 |
+
"sfreq": 250,
|
| 41 |
+
"split_seed": 2026,
|
| 42 |
+
"subtype_labels": [
|
| 43 |
+
"Centro-Parietal-IED",
|
| 44 |
+
"Frontal-IED",
|
| 45 |
+
"Generalized-IED",
|
| 46 |
+
"Occipital-IED",
|
| 47 |
+
"Temporal-IED"
|
| 48 |
+
],
|
| 49 |
+
"test_metrics": {
|
| 50 |
+
"accuracy": 0.9201637666325486,
|
| 51 |
+
"auprc": 0.6847588452968619,
|
| 52 |
+
"auroc": 0.8793068455863041,
|
| 53 |
+
"balanced_accuracy": 0.7937914036861053,
|
| 54 |
+
"f1": 0.6372093023255814,
|
| 55 |
+
"false_positives_per_minute": 0.6563039723661486,
|
| 56 |
+
"fn": 160,
|
| 57 |
+
"fp": 152,
|
| 58 |
+
"n": 3908,
|
| 59 |
+
"precision": 0.6431924882629108,
|
| 60 |
+
"prevalence": 0.11105424769703173,
|
| 61 |
+
"sensitivity": 0.631336405529954,
|
| 62 |
+
"specificity": 0.9562464018422567,
|
| 63 |
+
"threshold": 0.6004377007484436,
|
| 64 |
+
"tn": 3322,
|
| 65 |
+
"tp": 274
|
| 66 |
+
},
|
| 67 |
+
"validation_metrics": {
|
| 68 |
+
"accuracy": 0.9502308876346844,
|
| 69 |
+
"auprc": 0.6136477369244443,
|
| 70 |
+
"auroc": 0.8519717080479754,
|
| 71 |
+
"balanced_accuracy": 0.752188332661903,
|
| 72 |
+
"f1": 0.6269230769230769,
|
| 73 |
+
"false_positives_per_minute": 0.17169179229480735,
|
| 74 |
+
"fn": 153,
|
| 75 |
+
"fp": 41,
|
| 76 |
+
"n": 3898,
|
| 77 |
+
"precision": 0.7990196078431373,
|
| 78 |
+
"prevalence": 0.08106721395587481,
|
| 79 |
+
"sensitivity": 0.5158227848101266,
|
| 80 |
+
"specificity": 0.9885538805136795,
|
| 81 |
+
"threshold": 0.6004377007484436,
|
| 82 |
+
"tn": 3541,
|
| 83 |
+
"tp": 163
|
| 84 |
+
},
|
| 85 |
+
"validation_threshold_f1": 0.6004377007484436,
|
| 86 |
+
"validation_threshold_sensitivity80": 0.017428677529096603,
|
| 87 |
+
"window_samples": 1000,
|
| 88 |
+
"window_seconds": 4.0
|
| 89 |
+
}
|
pytorch/iednet_lite/model.pth
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:383fe1de4aa8cf01b5f1c0a6734d92214ec0e7f96a6cd18a50fa52c52ccb210c
|
| 3 |
+
size 1041083
|
pytorch/resnet_attention/config.json
ADDED
|
@@ -0,0 +1,89 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"binary_labels": [
|
| 3 |
+
"Non-IED",
|
| 4 |
+
"IED"
|
| 5 |
+
],
|
| 6 |
+
"channels": [
|
| 7 |
+
"Fp1",
|
| 8 |
+
"Fp2",
|
| 9 |
+
"F3",
|
| 10 |
+
"F4",
|
| 11 |
+
"C3",
|
| 12 |
+
"C4",
|
| 13 |
+
"P3",
|
| 14 |
+
"P4",
|
| 15 |
+
"O1",
|
| 16 |
+
"O2",
|
| 17 |
+
"F7",
|
| 18 |
+
"F8",
|
| 19 |
+
"T3",
|
| 20 |
+
"T4",
|
| 21 |
+
"T5",
|
| 22 |
+
"T6",
|
| 23 |
+
"Fz",
|
| 24 |
+
"Cz",
|
| 25 |
+
"Pz"
|
| 26 |
+
],
|
| 27 |
+
"epoch": 11,
|
| 28 |
+
"format_version": 1,
|
| 29 |
+
"model_name": "resnet_attention",
|
| 30 |
+
"parameters": 4989538,
|
| 31 |
+
"preprocessing": {
|
| 32 |
+
"bandpass_hz": [
|
| 33 |
+
1.0,
|
| 34 |
+
45.0
|
| 35 |
+
],
|
| 36 |
+
"normalization": "global_window_zscore_clip8",
|
| 37 |
+
"reference": "average"
|
| 38 |
+
},
|
| 39 |
+
"release_name": "resnet_attention",
|
| 40 |
+
"sfreq": 250,
|
| 41 |
+
"split_seed": 2026,
|
| 42 |
+
"subtype_labels": [
|
| 43 |
+
"Centro-Parietal-IED",
|
| 44 |
+
"Frontal-IED",
|
| 45 |
+
"Generalized-IED",
|
| 46 |
+
"Occipital-IED",
|
| 47 |
+
"Temporal-IED"
|
| 48 |
+
],
|
| 49 |
+
"test_metrics": {
|
| 50 |
+
"accuracy": 0.932446264073695,
|
| 51 |
+
"auprc": 0.7018171290865336,
|
| 52 |
+
"auroc": 0.8998458595650639,
|
| 53 |
+
"balanced_accuracy": 0.7906183923232226,
|
| 54 |
+
"f1": 0.6666666666666666,
|
| 55 |
+
"false_positives_per_minute": 0.4058721934369603,
|
| 56 |
+
"fn": 170,
|
| 57 |
+
"fp": 94,
|
| 58 |
+
"n": 3908,
|
| 59 |
+
"precision": 0.7374301675977654,
|
| 60 |
+
"prevalence": 0.11105424769703173,
|
| 61 |
+
"sensitivity": 0.6082949308755761,
|
| 62 |
+
"specificity": 0.9729418537708693,
|
| 63 |
+
"threshold": 0.6944227814674377,
|
| 64 |
+
"tn": 3380,
|
| 65 |
+
"tp": 264
|
| 66 |
+
},
|
| 67 |
+
"validation_metrics": {
|
| 68 |
+
"accuracy": 0.9461262185736276,
|
| 69 |
+
"auprc": 0.5585556572790095,
|
| 70 |
+
"auroc": 0.7953542324845042,
|
| 71 |
+
"balanced_accuracy": 0.741298793545788,
|
| 72 |
+
"f1": 0.5992366412213741,
|
| 73 |
+
"false_positives_per_minute": 0.21356783919597988,
|
| 74 |
+
"fn": 159,
|
| 75 |
+
"fp": 51,
|
| 76 |
+
"n": 3898,
|
| 77 |
+
"precision": 0.7548076923076923,
|
| 78 |
+
"prevalence": 0.08106721395587481,
|
| 79 |
+
"sensitivity": 0.49683544303797467,
|
| 80 |
+
"specificity": 0.9857621440536013,
|
| 81 |
+
"threshold": 0.6944227814674377,
|
| 82 |
+
"tn": 3531,
|
| 83 |
+
"tp": 157
|
| 84 |
+
},
|
| 85 |
+
"validation_threshold_f1": 0.6944227814674377,
|
| 86 |
+
"validation_threshold_sensitivity80": 0.007963554933667183,
|
| 87 |
+
"window_samples": 1000,
|
| 88 |
+
"window_seconds": 4.0
|
| 89 |
+
}
|
pytorch/resnet_attention/model.pth
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:df088c4fd087b453ef3aca76f54a975a69f015324f8d862d2d4ba5316cb91e1a
|
| 3 |
+
size 20037039
|
results/benchmark_results.csv
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
model,threshold,n,prevalence,auroc,auprc,accuracy,balanced_accuracy,f1,precision,sensitivity,specificity,false_positives_per_minute,tn,fp,fn,tp
|
| 2 |
+
iednet_lite,0.6004377007484436,3908,0.11105424769703173,0.8793068455863041,0.6847588452968619,0.9201637666325486,0.7937914036861053,0.6372093023255814,0.6431924882629108,0.631336405529954,0.9562464018422567,0.6563039723661486,3322,152,160,274
|
| 3 |
+
resnet_attention,0.6944227814674377,3908,0.11105424769703173,0.8998458595650639,0.7018171290865336,0.932446264073695,0.7906183923232226,0.6666666666666666,0.7374301675977654,0.6082949308755761,0.9729418537708693,0.4058721934369603,3380,94,170,264
|
| 4 |
+
eegpt,0.8916015625,3908,0.11105424769703173,0.8640698911466086,0.65324809994628,0.9201637666325486,0.767579570688379,0.6138613861386139,0.6631016042780749,0.5714285714285714,0.9637305699481865,0.5440414507772021,3348,126,186,248
|
| 5 |
+
eegdino_medium,0.619140625,3908,0.11105424769703173,0.9362860777493904,0.8019567240287226,0.9398669396110543,0.8683870171836076,0.7414741474147415,0.7094736842105264,0.7764976958525346,0.9602763385146805,0.5958549222797928,3336,138,97,337
|
| 6 |
+
ensemble_eegpt,0.5290762755,3908,0.11105424769703173,0.9004447787249058,0.7451331821970812,0.936028659160696,0.8248940781950977,0.7030878859857482,0.7254901960784313,0.6820276497695853,0.9677605066206102,0.4835924006908463,3362,112,138,296
|
| 7 |
+
ensemble_eegdino,0.5274549233333333,3908,0.11105424769703173,0.9261677928734589,0.7895565635533457,0.9437052200614124,0.8463503736778014,0.7399527186761229,0.7597087378640777,0.7211981566820277,0.9715025906735751,0.4274611398963731,3375,99,121,313
|
| 8 |
+
ensemble_4model,0.5273836475,3908,0.11105424769703173,0.9200864088462283,0.7888269120699914,0.9483111566018424,0.8600306689058151,0.7623529411764706,0.7788461538461539,0.7465437788018433,0.973517559009787,0.39723661485319517,3382,92,110,324
|
results/model_comparison.png
ADDED
|