notebook update

This commit is contained in:
2026-04-21 22:24:23 -05:00
parent 5b1b600979
commit 4e0eb1f5f0

View File

@@ -24,10 +24,10 @@
"id": "578c9128", "id": "578c9128",
"metadata": { "metadata": {
"execution": { "execution": {
"iopub.execute_input": "2026-04-22T03:09:19.274466Z", "iopub.execute_input": "2026-04-22T03:20:38.290986Z",
"iopub.status.busy": "2026-04-22T03:09:19.273226Z", "iopub.status.busy": "2026-04-22T03:20:38.290709Z",
"iopub.status.idle": "2026-04-22T03:09:19.280048Z", "iopub.status.idle": "2026-04-22T03:20:38.297801Z",
"shell.execute_reply": "2026-04-22T03:09:19.279206Z" "shell.execute_reply": "2026-04-22T03:20:38.296447Z"
} }
}, },
"outputs": [], "outputs": [],
@@ -42,10 +42,10 @@
"id": "857b22c0", "id": "857b22c0",
"metadata": { "metadata": {
"execution": { "execution": {
"iopub.execute_input": "2026-04-22T03:09:19.283002Z", "iopub.execute_input": "2026-04-22T03:20:38.300669Z",
"iopub.status.busy": "2026-04-22T03:09:19.282756Z", "iopub.status.busy": "2026-04-22T03:20:38.300420Z",
"iopub.status.idle": "2026-04-22T03:09:20.342432Z", "iopub.status.idle": "2026-04-22T03:20:39.360556Z",
"shell.execute_reply": "2026-04-22T03:09:20.341961Z" "shell.execute_reply": "2026-04-22T03:20:39.360152Z"
} }
}, },
"outputs": [], "outputs": [],
@@ -77,10 +77,10 @@
"id": "dc4b2c55", "id": "dc4b2c55",
"metadata": { "metadata": {
"execution": { "execution": {
"iopub.execute_input": "2026-04-22T03:09:20.343870Z", "iopub.execute_input": "2026-04-22T03:20:39.362226Z",
"iopub.status.busy": "2026-04-22T03:09:20.343760Z", "iopub.status.busy": "2026-04-22T03:20:39.362118Z",
"iopub.status.idle": "2026-04-22T03:09:20.346854Z", "iopub.status.idle": "2026-04-22T03:20:39.364971Z",
"shell.execute_reply": "2026-04-22T03:09:20.346506Z" "shell.execute_reply": "2026-04-22T03:20:39.364522Z"
} }
}, },
"outputs": [], "outputs": [],
@@ -118,21 +118,11 @@
"MOTOR_CH = ['C3', 'Cz', 'C4']\n", "MOTOR_CH = ['C3', 'Cz', 'C4']\n",
"MU_BAND = (8, 13)\n", "MU_BAND = (8, 13)\n",
"\n", "\n",
"# Sessions are stored with compound key \"{ses_id}_{kind}_{stim}\" and looked up\n",
"# positionally: idx=0 is the first session of that (kind, stim) type sorted by\n",
"# key, idx=1 is the second. This handles subjects like 002 where OFFLINE and\n",
"# ONLINE sessions share the same ses-Sxxx number in the filename.\n",
"PAIRS = [\n", "PAIRS = [\n",
" {'name': 'Pair1 (train=OFFLINE_FES)',\n", " {'name': 'Pair1 (train=OFFLINE_FES)',\n",
" 'train': {'kind': 'OFFLINE', 'stim': 'FES', 'idx': 0},\n", " 'train': 'S001', 'online_fes': 'S002', 'online_nofes': 'S003'},\n",
" 'online_fes': {'kind': 'ONLINE', 'stim': 'FES', 'idx': 0},\n",
" 'online_nofes': {'kind': 'ONLINE', 'stim': 'NOFES', 'idx': 0},\n",
" },\n",
" {'name': 'Pair2 (train=OFFLINE_NOFES)',\n", " {'name': 'Pair2 (train=OFFLINE_NOFES)',\n",
" 'train': {'kind': 'OFFLINE', 'stim': 'NOFES', 'idx': 0},\n", " 'train': 'S004', 'online_fes': 'S006', 'online_nofes': 'S005'},\n",
" 'online_fes': {'kind': 'ONLINE', 'stim': 'FES', 'idx': 1},\n",
" 'online_nofes': {'kind': 'ONLINE', 'stim': 'NOFES', 'idx': 1},\n",
" },\n",
"]" "]"
] ]
}, },
@@ -150,10 +140,10 @@
"id": "e798b039", "id": "e798b039",
"metadata": { "metadata": {
"execution": { "execution": {
"iopub.execute_input": "2026-04-22T03:09:20.348424Z", "iopub.execute_input": "2026-04-22T03:20:39.366319Z",
"iopub.status.busy": "2026-04-22T03:09:20.348357Z", "iopub.status.busy": "2026-04-22T03:20:39.366243Z",
"iopub.status.idle": "2026-04-22T03:09:20.361153Z", "iopub.status.idle": "2026-04-22T03:20:39.379175Z",
"shell.execute_reply": "2026-04-22T03:09:20.360830Z" "shell.execute_reply": "2026-04-22T03:20:39.378716Z"
} }
}, },
"outputs": [], "outputs": [],
@@ -391,10 +381,10 @@
"id": "d266216b", "id": "d266216b",
"metadata": { "metadata": {
"execution": { "execution": {
"iopub.execute_input": "2026-04-22T03:09:20.362577Z", "iopub.execute_input": "2026-04-22T03:20:39.380444Z",
"iopub.status.busy": "2026-04-22T03:09:20.362504Z", "iopub.status.busy": "2026-04-22T03:20:39.380365Z",
"iopub.status.idle": "2026-04-22T03:09:59.345624Z", "iopub.status.idle": "2026-04-22T03:21:10.042926Z",
"shell.execute_reply": "2026-04-22T03:09:59.271366Z" "shell.execute_reply": "2026-04-22T03:21:10.041519Z"
} }
}, },
"outputs": [ "outputs": [
@@ -404,174 +394,30 @@
"text": [ "text": [
"Found 24 XDF file(s).\n", "Found 24 XDF file(s).\n",
"Preprocessing: notch 60 Hz → CAR → bandpass 830 Hz → baseline-correct → PTP-reject @ 100 µV\n", "Preprocessing: notch 60 Hz → CAR → bandpass 830 Hz → baseline-correct → PTP-reject @ 100 µV\n",
"\n" "\n",
] " 002/S001 OFFLINE FES n= 85 (MI=43, REST=42) rej=5\n",
}, " 002/S002 ONLINE FES n= 53 (MI=27, REST=26) rej=7 mk_acc=0.883\n",
{ " 002/S003 ONLINE NOFES n= 52 (MI=26, REST=26) rej=8 mk_acc=0.833\n",
"name": "stdout", " 002/S004 OFFLINE NOFES n= 90 (MI=45, REST=45) rej=0\n",
"output_type": "stream", " 002/S005 ONLINE NOFES n= 60 (MI=30, REST=30) rej=0 mk_acc=0.850\n",
"text": [ " 002/S006 ONLINE FES n= 56 (MI=27, REST=29) rej=4 mk_acc=0.917\n",
" 002/S001 OFFLINE FES n= 85 (MI=43, REST=42) rej=5\n" " 003/S001 OFFLINE FES n= 89 (MI=44, REST=45) rej=1\n",
] " 003/S002 ONLINE FES n= 59 (MI=29, REST=30) rej=1 mk_acc=0.750\n",
}, " 003/S003 ONLINE NOFES n= 38 (MI=17, REST=21) rej=0 mk_acc=0.763\n",
{ " 003/S004 OFFLINE NOFES n= 86 (MI=42, REST=44) rej=4\n",
"name": "stdout", " 003/S005 ONLINE NOFES n= 43 (MI=19, REST=24) rej=17 mk_acc=0.717\n",
"output_type": "stream", " 003/S006 ONLINE FES n= 52 (MI=23, REST=29) rej=8 mk_acc=0.767\n",
"text": [ " 005/S001 OFFLINE FES n= 90 (MI=45, REST=45) rej=0\n",
" 002/S002 ONLINE FES n= 53 (MI=27, REST=26) rej=7 mk_acc=0.883\n" " 005/S002 ONLINE FES n= 60 (MI=30, REST=30) rej=0 mk_acc=0.800\n",
] " 005/S003 ONLINE NOFES n= 59 (MI=29, REST=30) rej=1 mk_acc=0.933\n",
}, " 005/S004 OFFLINE NOFES n= 89 (MI=44, REST=45) rej=1\n",
{ " 005/S005 ONLINE NOFES n= 58 (MI=28, REST=30) rej=2 mk_acc=0.783\n",
"name": "stdout", " 005/S006 ONLINE FES n= 59 (MI=30, REST=29) rej=1 mk_acc=0.917\n",
"output_type": "stream", " 009/S001 OFFLINE FES n= 57 (MI=33, REST=24) rej=33\n",
"text": [ " 009/S002 ONLINE FES n= 42 (MI=21, REST=21) rej=18 mk_acc=0.717\n",
" 002/S003 ONLINE NOFES n= 52 (MI=26, REST=26) rej=8 mk_acc=0.833\n" " 009/S003 ONLINE NOFES n= 1 (MI=1, REST=0) rej=59 mk_acc=0.717\n",
] " 009/S004 OFFLINE NOFES n= 86 (MI=42, REST=44) rej=4\n",
}, " 009/S005 ONLINE NOFES n= 60 (MI=30, REST=30) rej=0 mk_acc=0.850\n",
{
"name": "stdout",
"output_type": "stream",
"text": [
" 002/S004 OFFLINE NOFES n= 90 (MI=45, REST=45) rej=0\n"
]
},
{
"name": "stdout",
"output_type": "stream",
"text": [
" 002/S005 ONLINE NOFES n= 60 (MI=30, REST=30) rej=0 mk_acc=0.850\n"
]
},
{
"name": "stdout",
"output_type": "stream",
"text": [
" 002/S006 ONLINE FES n= 56 (MI=27, REST=29) rej=4 mk_acc=0.917\n"
]
},
{
"name": "stdout",
"output_type": "stream",
"text": [
" 003/S001 OFFLINE FES n= 89 (MI=44, REST=45) rej=1\n"
]
},
{
"name": "stdout",
"output_type": "stream",
"text": [
" 003/S002 ONLINE FES n= 59 (MI=29, REST=30) rej=1 mk_acc=0.750\n"
]
},
{
"name": "stdout",
"output_type": "stream",
"text": [
" 003/S003 ONLINE NOFES n= 38 (MI=17, REST=21) rej=0 mk_acc=0.763\n"
]
},
{
"name": "stdout",
"output_type": "stream",
"text": [
" 003/S004 OFFLINE NOFES n= 86 (MI=42, REST=44) rej=4\n"
]
},
{
"name": "stdout",
"output_type": "stream",
"text": [
" 003/S005 ONLINE NOFES n= 43 (MI=19, REST=24) rej=17 mk_acc=0.717\n"
]
},
{
"name": "stdout",
"output_type": "stream",
"text": [
" 003/S006 ONLINE FES n= 52 (MI=23, REST=29) rej=8 mk_acc=0.767\n"
]
},
{
"name": "stdout",
"output_type": "stream",
"text": [
" 005/S001 OFFLINE FES n= 90 (MI=45, REST=45) rej=0\n"
]
},
{
"name": "stdout",
"output_type": "stream",
"text": [
" 005/S002 ONLINE FES n= 60 (MI=30, REST=30) rej=0 mk_acc=0.800\n"
]
},
{
"name": "stdout",
"output_type": "stream",
"text": [
" 005/S003 ONLINE NOFES n= 59 (MI=29, REST=30) rej=1 mk_acc=0.933\n"
]
},
{
"name": "stdout",
"output_type": "stream",
"text": [
" 005/S004 OFFLINE NOFES n= 89 (MI=44, REST=45) rej=1\n"
]
},
{
"name": "stdout",
"output_type": "stream",
"text": [
" 005/S005 ONLINE NOFES n= 58 (MI=28, REST=30) rej=2 mk_acc=0.783\n"
]
},
{
"name": "stdout",
"output_type": "stream",
"text": [
" 005/S006 ONLINE FES n= 59 (MI=30, REST=29) rej=1 mk_acc=0.917\n"
]
},
{
"name": "stdout",
"output_type": "stream",
"text": [
" 009/S001 OFFLINE FES n= 57 (MI=33, REST=24) rej=33\n"
]
},
{
"name": "stdout",
"output_type": "stream",
"text": [
" 009/S002 ONLINE FES n= 42 (MI=21, REST=21) rej=18 mk_acc=0.717\n"
]
},
{
"name": "stdout",
"output_type": "stream",
"text": [
" 009/S003 ONLINE NOFES n= 1 (MI=1, REST=0) rej=59 mk_acc=0.717\n"
]
},
{
"name": "stdout",
"output_type": "stream",
"text": [
" 009/S004 OFFLINE NOFES n= 86 (MI=42, REST=44) rej=4\n"
]
},
{
"name": "stdout",
"output_type": "stream",
"text": [
" 009/S005 ONLINE NOFES n= 60 (MI=30, REST=30) rej=0 mk_acc=0.850\n"
]
},
{
"name": "stdout",
"output_type": "stream",
"text": [
" 009/S006 ONLINE FES n= 50 (MI=26, REST=24) rej=10 mk_acc=0.817\n", " 009/S006 ONLINE FES n= 50 (MI=26, REST=24) rej=10 mk_acc=0.817\n",
"\n", "\n",
"Loaded 4 subject(s): ['002', '003', '005', '009'] | total artifact-rejected epochs: 184\n" "Loaded 4 subject(s): ['002', '003', '005', '009'] | total artifact-rejected epochs: 184\n"
@@ -600,9 +446,7 @@
" print(f' ERROR {os.path.basename(fp)}: {e}')\n", " print(f' ERROR {os.path.basename(fp)}: {e}')\n",
" continue\n", " continue\n",
"\n", "\n",
" # Compound key prevents collision when multiple session types share the same ses_id\n", " sessions.setdefault(subj, {})[ses_id] = dict(\n",
" key = f'{ses_id}_{kind}_{stim}'\n",
" sessions.setdefault(subj, {})[key] = dict(\n",
" X=X, y=y, kind=kind, stim=stim,\n", " X=X, y=y, kind=kind, stim=stim,\n",
" ch_names=ch_names, sfreq=sfreq,\n", " ch_names=ch_names, sfreq=sfreq,\n",
" mk_acc=mk_acc, file=os.path.basename(fp))\n", " mk_acc=mk_acc, file=os.path.basename(fp))\n",
@@ -615,16 +459,7 @@
"\n", "\n",
"subjects = sorted(sessions.keys())\n", "subjects = sorted(sessions.keys())\n",
"print(f'\\nLoaded {len(subjects)} subject(s): {subjects} | '\n", "print(f'\\nLoaded {len(subjects)} subject(s): {subjects} | '\n",
" f'total artifact-rejected epochs: {total_rej}')\n", " f'total artifact-rejected epochs: {total_rej}')"
"\n",
"\n",
"def pick_session(subj_ses, kind, stim, idx):\n",
" \"\"\"Return the idx-th session matching (kind, stim), sorted by key. None if not enough.\"\"\"\n",
" matches = sorted(\n",
" (v for v in subj_ses.values() if v['kind'] == kind and v['stim'] == stim),\n",
" key=lambda v: v['file'] # alphabetical file order = recording order\n",
" )\n",
" return matches[idx] if idx < len(matches) else None"
] ]
}, },
{ {
@@ -641,10 +476,10 @@
"id": "611baf23", "id": "611baf23",
"metadata": { "metadata": {
"execution": { "execution": {
"iopub.execute_input": "2026-04-22T03:09:59.485105Z", "iopub.execute_input": "2026-04-22T03:21:10.051665Z",
"iopub.status.busy": "2026-04-22T03:09:59.481587Z", "iopub.status.busy": "2026-04-22T03:21:10.051368Z",
"iopub.status.idle": "2026-04-22T03:09:59.616890Z", "iopub.status.idle": "2026-04-22T03:21:10.057208Z",
"shell.execute_reply": "2026-04-22T03:09:59.612929Z" "shell.execute_reply": "2026-04-22T03:21:10.056834Z"
} }
}, },
"outputs": [ "outputs": [
@@ -692,10 +527,10 @@
"id": "f5e80da3", "id": "f5e80da3",
"metadata": { "metadata": {
"execution": { "execution": {
"iopub.execute_input": "2026-04-22T03:09:59.637480Z", "iopub.execute_input": "2026-04-22T03:21:10.059575Z",
"iopub.status.busy": "2026-04-22T03:09:59.637343Z", "iopub.status.busy": "2026-04-22T03:21:10.059490Z",
"iopub.status.idle": "2026-04-22T03:10:03.257068Z", "iopub.status.idle": "2026-04-22T03:21:11.605957Z",
"shell.execute_reply": "2026-04-22T03:10:03.255453Z" "shell.execute_reply": "2026-04-22T03:21:11.605570Z"
} }
}, },
"outputs": [ "outputs": [
@@ -734,15 +569,13 @@
" subj_ses = sessions[subj]\n", " subj_ses = sessions[subj]\n",
"\n", "\n",
" for pair in PAIRS:\n", " for pair in PAIRS:\n",
" train = pick_session(subj_ses, pair['train']['kind'], pair['train']['stim'], pair['train']['idx'])\n", " needed = (pair['train'], pair['online_fes'], pair['online_nofes'])\n",
" te_f = pick_session(subj_ses, pair['online_fes']['kind'], pair['online_fes']['stim'], pair['online_fes']['idx'])\n", " missing = [k for k in needed if k not in subj_ses]\n",
" te_n = pick_session(subj_ses, pair['online_nofes']['kind'], pair['online_nofes']['stim'], pair['online_nofes']['idx'])\n",
"\n",
" missing = [role for role, ses in [('train', train), ('online_fes', te_f), ('online_nofes', te_n)] if ses is None]\n",
" if missing:\n", " if missing:\n",
" print(f'[{subj}] {pair[\"name\"]}: no session found for {missing} — skipping')\n", " print(f'[{subj}] {pair[\"name\"]}: missing {missing} — skipping')\n",
" continue\n", " continue\n",
"\n", "\n",
" train = subj_ses[pair['train']]\n",
" if set(np.unique(train['y'])) != {0, 1}:\n", " if set(np.unique(train['y'])) != {0, 1}:\n",
" print(f'[{subj}] {pair[\"name\"]}: training set lacks both classes — skipping')\n", " print(f'[{subj}] {pair[\"name\"]}: training set lacks both classes — skipping')\n",
" continue\n", " continue\n",
@@ -750,7 +583,8 @@
" clf = CSPLDA(n_csp=N_CSP).fit(train['X'], train['y'])\n", " clf = CSPLDA(n_csp=N_CSP).fit(train['X'], train['y'])\n",
" train_acc = (clf.predict(train['X']) == train['y']).mean()\n", " train_acc = (clf.predict(train['X']) == train['y']).mean()\n",
"\n", "\n",
" for te, cond_label in [(te_f, 'FES'), (te_n, 'NOFES')]:\n", " for cond_key, cond_label in [('online_fes', 'FES'), ('online_nofes', 'NOFES')]:\n",
" te = subj_ses[pair[cond_key]]\n",
" acc = te['mk_acc']\n", " acc = te['mk_acc']\n",
" if acc is None:\n", " if acc is None:\n",
" print(f'[{subj}] {pair[\"name\"]} / {cond_label}: no EARLYSTOP markers — skipping')\n", " print(f'[{subj}] {pair[\"name\"]} / {cond_label}: no EARLYSTOP markers — skipping')\n",
@@ -798,10 +632,10 @@
"id": "d53e63b9", "id": "d53e63b9",
"metadata": { "metadata": {
"execution": { "execution": {
"iopub.execute_input": "2026-04-22T03:10:03.262351Z", "iopub.execute_input": "2026-04-22T03:21:11.609669Z",
"iopub.status.busy": "2026-04-22T03:10:03.262228Z", "iopub.status.busy": "2026-04-22T03:21:11.609563Z",
"iopub.status.idle": "2026-04-22T03:10:04.250444Z", "iopub.status.idle": "2026-04-22T03:21:12.097953Z",
"shell.execute_reply": "2026-04-22T03:10:04.249983Z" "shell.execute_reply": "2026-04-22T03:21:12.097507Z"
} }
}, },
"outputs": [ "outputs": [
@@ -885,10 +719,10 @@
"id": "393042a0", "id": "393042a0",
"metadata": { "metadata": {
"execution": { "execution": {
"iopub.execute_input": "2026-04-22T03:10:04.254162Z", "iopub.execute_input": "2026-04-22T03:21:12.099369Z",
"iopub.status.busy": "2026-04-22T03:10:04.254059Z", "iopub.status.busy": "2026-04-22T03:21:12.099281Z",
"iopub.status.idle": "2026-04-22T03:10:05.210590Z", "iopub.status.idle": "2026-04-22T03:21:12.990027Z",
"shell.execute_reply": "2026-04-22T03:10:05.210168Z" "shell.execute_reply": "2026-04-22T03:21:12.989595Z"
} }
}, },
"outputs": [ "outputs": [
@@ -963,10 +797,10 @@
"id": "75df404b", "id": "75df404b",
"metadata": { "metadata": {
"execution": { "execution": {
"iopub.execute_input": "2026-04-22T03:10:05.215990Z", "iopub.execute_input": "2026-04-22T03:21:12.992553Z",
"iopub.status.busy": "2026-04-22T03:10:05.215881Z", "iopub.status.busy": "2026-04-22T03:21:12.992447Z",
"iopub.status.idle": "2026-04-22T03:10:05.485686Z", "iopub.status.idle": "2026-04-22T03:21:13.261572Z",
"shell.execute_reply": "2026-04-22T03:10:05.485120Z" "shell.execute_reply": "2026-04-22T03:21:13.261249Z"
} }
}, },
"outputs": [ "outputs": [
@@ -1035,10 +869,10 @@
"id": "cf55268e", "id": "cf55268e",
"metadata": { "metadata": {
"execution": { "execution": {
"iopub.execute_input": "2026-04-22T03:10:05.496520Z", "iopub.execute_input": "2026-04-22T03:21:13.263110Z",
"iopub.status.busy": "2026-04-22T03:10:05.496412Z", "iopub.status.busy": "2026-04-22T03:21:13.263012Z",
"iopub.status.idle": "2026-04-22T03:10:05.506439Z", "iopub.status.idle": "2026-04-22T03:21:13.268334Z",
"shell.execute_reply": "2026-04-22T03:10:05.506014Z" "shell.execute_reply": "2026-04-22T03:21:13.267954Z"
} }
}, },
"outputs": [ "outputs": [