{"metadata":{"kernelspec":{"language":"python","display_name":"Python 3","name":"python3"},"language_info":{"name":"python","version":"3.10.13","mimetype":"text/x-python","codemirror_mode":{"name":"ipython","version":3},"pygments_lexer":"ipython3","nbconvert_exporter":"python","file_extension":".py"},"kaggle":{"accelerator":"none","dataSources":[{"sourceId":59093,"databundleVersionId":7469972,"sourceType":"competition"}],"dockerImageVersionId":30646,"isInternetEnabled":true,"language":"python","sourceType":"notebook","isGpuEnabled":false}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"markdown","source":"# HMS - Harmful Brain Activity Classification\nClassify seizures and other patterns of harmful brain activity in critically ill patients\n\n중환자의 발작 및 기타 유해한 뇌 활동 패턴","metadata":{}},{"cell_type":"markdown","source":"## Overview\n\n이 경진의 주요 과제는 뇌졸중과 다른 유형의 뇌 활동을 감지하는 것입니다. 여러분은 병원에 입원한 환자의 뇌파(EEG) 신호를 바탕을 모델을 개발하게 될 것입니다. \n\n이 작업을 통해 뇌파 패턴 분류 정확성을 빠르게 개선하여 신경학적 치료, 간질, 약물 개발에 획기적 이점을 제공할 것입니다. 이 분야의 발전을 통해 담당의와 뇌 연구자는 뇌졸중이나 다른 뇌 손상을 조기에 발견하여 더 빠르고 정확한 치료를 제공할 것입니다.","metadata":{}},{"cell_type":"markdown","source":"## Description\n\n청진기에서 혀 억제제까지 의사는 환자를 치료하기 위해 다양한 도구를 사용합니다. 의사는 중환자에게 뇌파를 사용하여 뇌졸중과 뇌 손상을 일으킬 수 있는 다른 유형의 뇌 활동을 감지합니다. 다음 동영상에서 의사가 이러한 EEG 신호를 해석하는 방법에 대해 알아볼 수 있습니다.\n\nEEG Talk - ACNS Critical Care EEG Terminology 2021 [(Part 1)](https://www.youtube.com/watch?v=S9NLrhj0x-M&t) [(Part 2)](https://www.youtube.com/watch?v=4D9R2WIKr-A) [(Part 3)](https://www.youtube.com/watch?v=-R5yUX7p_j4) [(Part 4)](https://www.youtube.com/watch?v=OknS2ObD9-g&t) [(Part 5)](https://www.youtube.com/watch?v=2c7ABQRkn3s)\n\n현재 EEG 모니터링은 전문 신경학자의 수동 분석에만 의존합니다. 이는 매우 귀중한 작업이지만, 이러한 노동 집약적 프로세스는 주요 병목 현상입니다. 시간이 많이 걸릴 뿐만 아니라 EEG 기록의 수동 검토는 비용이 많이 들고, 피로로 인한 오류가 발생하기 쉽고, 심지어 검토자가 전문가인 경우에도 검토자 간의 신뢰성 문제가 있습니다.\n\n이 경진에서 관심을 갖는 패턴은 여섯 가지입니다: 발작(SZ), 전반적 주기적 방전(GPD), 측면적 주기적 방전(LPD), 측면적 리드미컬 델타 활동(LRDA), 전반적 리드미컬 델타 활동(GRDA), 또는 \"기타\". 이러한 패턴에 대한 자세한 설명은 [여기에서 확인할 수 있습니다.](https://www.acns.org/UserFiles/file/ACNSStandardizedCriticalCareEEGTerminology_rev2021.pdf)\n\n이 경진에서 사용된 EEG 세그먼트는 전문가 그룹에 의해 주석 처리되거나 분류되었습니다. 어떤 경우에는 전문가가 올바른 레이블에 대해 완전히 동의합니다. 다른 경우에는 전문가가 동의하지 않습니다. 우리는 전문가 간의 합의 수준이 높은 세그먼트를 \"이상화된\" 패턴이라고 부릅니다. 전문가의 약 1/2가 \"기타\"로 레이블을 지정하고 나머지 5개 레이블 중 하나를 약 1/2가 지정하는 경우를 \"프로토 패턴\"이라고 합니다. 전문가가 지정한 5가지 패턴 중 2가지에 대해 전문가가 대략적으로 분할되는 경우를 \"에지 케이스\"라고 합니다.\n\n<figure>\n    <img src=\"https://www.researchgate.net/profile/Sebastian-Nagel-4/publication/338423585/figure/fig1/AS:844668573073409@1578396089381/Sketch-of-how-to-record-an-Electroencephalogram-An-EEG-allows-measuring-the-electrical.png\"\n         alt=\"EEG\">\n    <figcaption>Sketch of how to record an Electroencephalogram. An EEG allows measuring the electrical activity on the scalp using electrodes which are often fixated on an EEG cap. For each electrode, the signals are amplified and can be used in the following for a desired processing.</figcaption>\n</figure>\nImage from: Nagel, Sebastian. Towards a home-use BCI: fast asynchronous control and robust non-control state detection. Diss. Universität Tübingen, 2019.","metadata":{}},{"cell_type":"markdown","source":"## Dataset Description\n이 경진의 목표는 뇌파(EEG) 데이터에서 발작과 다른 유형의 유해한 뇌 활동을 감지하고 분류하는 것입니다. 전문가조차도 이것이 어려운 작업이라는 것을 알고 있으며 종종 올바른 레이블에 대해 의견이 일치하지 않습니다.\n\n이것은 코드 경진입니다. 테스트 세트의 몇 가지 예만 다운로드할 수 있습니다. 제출물이 채점되면 테스트 폴더는 전체 테스트 세트가 포함된 버전으로 바뀝니다.","metadata":{}},{"cell_type":"markdown","source":"### Files\n**train.csv** 학습 세트의 메타데이터. 전문 해설자는 50초 길이의 EEG 샘플과 10분 창에 중심을 맞춘 일치하는 스펙트로그램을 검토하고 중심 10초에 레이블을 지정했습니다. 이러한 샘플 중 많은 것이 겹쳐 통합되었습니다. `train.csv`는 평가자가 주석을 단 원래 하위 집합을 추출할 수 있는 메타데이터를 제공합니다.\n\n- `eeg_id` - 전체 EEG 기록에 대한 고유 식별자입니다.\n- `eeg_sub_id` - 이 행의 레이블이 적용되는 특정 50초 길이의 하위 샘플에 대한 ID입니다.\n- `eeg_label_offset_seconds` - 통합된 EEG와 이 하위 샘플 간의 시간입니다.\n- `spectrogram_id` - 전체 EEG 기록에 대한 고유 식별자입니다.\n- `spectrogram_sub_id` - 이 행의 레이블이 적용되는 특정 10분 하위 샘플에 대한 ID입니다.\n- `spectogram_label_offset_seconds` - 통합된 스펙트로그램과 이 하위 샘플 간의 시간입니다.\n- `label_id` - 이 레이블 세트에 대한 ID입니다.\n- `patient_id` - 데이터를 제공한 환자에 대한 ID입니다.\n- `expert_consensus` - 합의 해설자 레이블입니다. 편의를 위해서만 제공됩니다.\n- `[seizure/lpd/gpd/lrda/grda/other]_vote` - 주어진 뇌 활동 클래스에 대한 해설자 투표 수입니다. 활동 클래스의 전체 이름은 다음과 같습니다. `lpd`: 측면화된 주기적 방전, `gpd`: 일반화된 주기적 방전, `lrd`: 측면화된 리드미컬 델타 활동, `grda`: 일반화된 리드미컬 델타 활동입니다. 이러한 패턴에 대한 자세한 설명은 [여기에서 확인할 수 있습니다.](https://www.acns.org/UserFiles/file/ACNSStandardizedCriticalCareEEGTerminology_rev2021.pdf)\n\n**test.csv** 테스트 세트의 메타데이터입니다. 테스트 세트에는 겹치는 샘플이 없으므로 학습 메타데이터의 많은 열이 적용되지 않습니다.\n\n- `eeg_id`\n- `spectrogram_id`\n- `patient_id`\n\n**sample_submission.csv**\n\n- `eeg_id`\n- `[seizure/lpd/gpd/lrda/grda/other]_vote` - 대상 열입니다. 예측은 확률이어야 합니다. 테스트 샘플에는 3~20명의 해설자가 있었습니다.\n\n**train_eegs/** 하나 이상의 겹치는 샘플의 EEG 데이터입니다. **train.csv**의 메타데이터를 사용하여 특정 주석이 있는 하위 집합을 선택합니다. 열 이름은 [EEG 리드에 대한 개별 전극 위치의 이름](https://en.wikipedia.org/wiki/10%E2%80%9320_system_%28EEG%29)이며, 예외는 하나 있습니다. EKG 열은 심장에서 데이터를 기록하는 심전도 리드용입니다. 모든 EEG 데이터(학습 및 테스트 모두)는 초당 200개 샘플의 주파수로 수집되었습니다.\n\n**test_eegs/** 정확히 50초의 EEG 데이터입니다.\n\n**train_spectrograms/** 스펙트로그램으로 조립된 EEG 데이터입니다. **train.csv**의 메타데이터를 사용하여 특정 주석이 있는 하위 집합을 선택합니다. 열 이름은 헤르츠 단위의 주파수와 EEG 전극의 기록 영역을 나타냅니다. 후자는 LL = 왼쪽 측면, RL = 오른쪽 측면, LP = 왼쪽 반사상, RP = 오른쪽 반사상으로 약어로 표시됩니다.\n\n**test_spectrograms/** 정확히 10분의 EEG 데이터를 사용하여 조립된 스펙트로그램입니다.\n\n**example_figures/** 개요 탭에서 사용된 예제 사례 이미지의 더 큰 복사본입니다.","metadata":{}},{"cell_type":"markdown","source":"## EDA","metadata":{}},{"cell_type":"code","source":"import numpy as np\nimport pandas as pd\n\nBASE_PATH = '/kaggle/input/hms-harmful-brain-activity-classification'","metadata":{"execution":{"iopub.status.busy":"2024-02-18T12:44:13.759165Z","iopub.execute_input":"2024-02-18T12:44:13.760526Z","iopub.status.idle":"2024-02-18T12:44:14.205688Z","shell.execute_reply.started":"2024-02-18T12:44:13.760475Z","shell.execute_reply":"2024-02-18T12:44:14.204286Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"- `train.csv`","metadata":{}},{"cell_type":"code","source":"train_df = pd.read_csv(f'{BASE_PATH}/train.csv')\ntrain_df","metadata":{"execution":{"iopub.status.busy":"2024-02-18T12:44:14.207855Z","iopub.execute_input":"2024-02-18T12:44:14.208345Z","iopub.status.idle":"2024-02-18T12:44:14.530493Z","shell.execute_reply.started":"2024-02-18T12:44:14.208312Z","shell.execute_reply":"2024-02-18T12:44:14.529282Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"- `test.csv`","metadata":{}},{"cell_type":"code","source":"test_df = pd.read_csv(f'{BASE_PATH}/test.csv')\ntest_df","metadata":{"execution":{"iopub.status.busy":"2024-02-18T12:44:14.531628Z","iopub.execute_input":"2024-02-18T12:44:14.531968Z","iopub.status.idle":"2024-02-18T12:44:14.550274Z","shell.execute_reply.started":"2024-02-18T12:44:14.531940Z","shell.execute_reply":"2024-02-18T12:44:14.549228Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"- Tiny Tip: It's better to observe the test dataset first, rather than the train dataset.\n     - The test dataset gives us a clue about the shape of the model's input and output.","metadata":{}},{"cell_type":"markdown","source":"### Test dataset","metadata":{}},{"cell_type":"markdown","source":"#### EEG\n<figure>\n    <img src=\"https://www.researchgate.net/profile/Danny-Plass-Oude-Bos/publication/237777779/figure/fig3/AS:669556259434497@1536646060035/10-20-system-of-electrode-placement.png\"\n         alt=\"EEG2\">\n    <figcaption>10-20 system of electrode placement.</figcaption>\n</figure>\nImage from: Bos, Danny Oude. \"EEG-based emotion recognition.\" The influence of visual and auditory stimuli 56.3 (2006): 1-17.\n\nMore about EEG:\n- [Introduction to EEG](https://youtu.be/XMizSSOejg0)\n- https://www.learningeeg.com/montages-and-technical-components\n","metadata":{}},{"cell_type":"code","source":"eeg_id = test_df.loc[0, 'eeg_id']\neeg_df = pd.read_parquet(f'{BASE_PATH}/test_eegs/{eeg_id}.parquet')\neeg_df","metadata":{"execution":{"iopub.status.busy":"2024-02-18T12:44:14.552901Z","iopub.execute_input":"2024-02-18T12:44:14.553776Z","iopub.status.idle":"2024-02-18T12:44:14.779112Z","shell.execute_reply.started":"2024-02-18T12:44:14.553732Z","shell.execute_reply":"2024-02-18T12:44:14.777967Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"- 10,000 rows, 50 sec.","metadata":{}},{"cell_type":"code","source":"eeg_df.Fp1.plot()","metadata":{"execution":{"iopub.status.busy":"2024-02-18T12:44:14.780659Z","iopub.execute_input":"2024-02-18T12:44:14.781119Z","iopub.status.idle":"2024-02-18T12:44:15.109546Z","shell.execute_reply.started":"2024-02-18T12:44:14.781077Z","shell.execute_reply":"2024-02-18T12:44:15.108205Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"#### Spectrogram","metadata":{}},{"cell_type":"code","source":"spec_id = test_df.loc[0, 'spectrogram_id']\nspec_df = pd.read_parquet(f'{BASE_PATH}/test_spectrograms/{spec_id}.parquet')\nspec_df","metadata":{"execution":{"iopub.status.busy":"2024-02-18T12:44:15.111545Z","iopub.execute_input":"2024-02-18T12:44:15.112310Z","iopub.status.idle":"2024-02-18T12:44:15.207619Z","shell.execute_reply.started":"2024-02-18T12:44:15.112266Z","shell.execute_reply":"2024-02-18T12:44:15.205896Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"- 300 rows, 10 min. = 600 sec.","metadata":{}},{"cell_type":"markdown","source":"### Train dataset","metadata":{}},{"cell_type":"code","source":"eeg_id = train_df.loc[0, 'eeg_id']\neeg_df = pd.read_parquet(f'{BASE_PATH}/train_eegs/{eeg_id}.parquet')\neeg_df","metadata":{"execution":{"iopub.status.busy":"2024-02-18T12:44:15.209472Z","iopub.execute_input":"2024-02-18T12:44:15.209805Z","iopub.status.idle":"2024-02-18T12:44:15.283302Z","shell.execute_reply.started":"2024-02-18T12:44:15.209779Z","shell.execute_reply":"2024-02-18T12:44:15.281944Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"- 18,000 rows, 90 sec. (subject to change)","metadata":{}},{"cell_type":"code","source":"eeg_df.Fp1.plot()","metadata":{"execution":{"iopub.status.busy":"2024-02-18T12:44:15.284794Z","iopub.execute_input":"2024-02-18T12:44:15.285216Z","iopub.status.idle":"2024-02-18T12:44:15.573516Z","shell.execute_reply.started":"2024-02-18T12:44:15.285184Z","shell.execute_reply":"2024-02-18T12:44:15.572404Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"spec_id = train_df.loc[0, 'spectrogram_id']\nspec_df = pd.read_parquet(f'{BASE_PATH}/train_spectrograms/{spec_id}.parquet')\nspec_df","metadata":{"execution":{"iopub.status.busy":"2024-02-18T12:44:15.575058Z","iopub.execute_input":"2024-02-18T12:44:15.576093Z","iopub.status.idle":"2024-02-18T12:44:15.663753Z","shell.execute_reply.started":"2024-02-18T12:44:15.576050Z","shell.execute_reply":"2024-02-18T12:44:15.662667Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"spec_df.columns[201:401]","metadata":{"execution":{"iopub.status.busy":"2024-02-18T13:53:00.346609Z","iopub.execute_input":"2024-02-18T13:53:00.347072Z","iopub.status.idle":"2024-02-18T13:53:00.354816Z","shell.execute_reply.started":"2024-02-18T13:53:00.347038Z","shell.execute_reply":"2024-02-18T13:53:00.353815Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"- 320 rows, 640 sec. (subject to change)","metadata":{}},{"cell_type":"markdown","source":"### Utility\n\n- 50 sec. EEG\n- 10 min. spectrograms","metadata":{}},{"cell_type":"code","source":"from functools import cache\nfrom typing import Literal\n\neeg_label = eeg_df.columns.to_list()\nspec_label = ['LL', 'RL', 'LP', 'RP']\n\n@cache\ndef load_data(eeg_id, spectrogram_id, data_type:Literal['train','test']='train'):\n    eeg_df = pd.read_parquet(f'{BASE_PATH}/{data_type}_eegs/{eeg_id}.parquet')\n    spec_df = pd.read_parquet(f'{BASE_PATH}/{data_type}_spectrograms/{spectrogram_id}.parquet')\n    return eeg_df, spec_df\n\ndef get_array(eeg_id, spectrogram_id, eeg_offset=.0, spectrogram_offset=.0):\n    eeg_window = 10000\n    spec_window = 300\n    eeg_offset = int(eeg_offset * 200)\n    spec_offset = int(spectrogram_offset/2)\n    \n    eeg_df, spec_df = load_data(eeg_id, spectrogram_id)\n    eeg_arr = eeg_df.iloc[eeg_offset:eeg_offset+eeg_window].to_numpy('float32')\n    \n    spec_arr = spec_df.iloc[spec_offset:spec_offset+spec_window, 1:].T.to_numpy('float32')\n    spec_dict = {\n        k: spec_arr[i*100:(i+1)*100] for i, k in enumerate(spec_label)\n    }\n    \n    return eeg_arr, spec_dict","metadata":{"execution":{"iopub.status.busy":"2024-02-18T12:44:15.668133Z","iopub.execute_input":"2024-02-18T12:44:15.668504Z","iopub.status.idle":"2024-02-18T12:44:15.679710Z","shell.execute_reply.started":"2024-02-18T12:44:15.668474Z","shell.execute_reply":"2024-02-18T12:44:15.678531Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"### Example figures\n\nWhat features are used by the expert annotators (human doctors) to label?","metadata":{}},{"cell_type":"code","source":"train_df[(train_df.eeg_id == 1327593077) & (train_df.eeg_sub_id == 88)]","metadata":{"execution":{"iopub.status.busy":"2024-02-18T14:29:37.094073Z","iopub.execute_input":"2024-02-18T14:29:37.094902Z","iopub.status.idle":"2024-02-18T14:29:37.115853Z","shell.execute_reply.started":"2024-02-18T14:29:37.094863Z","shell.execute_reply":"2024-02-18T14:29:37.114524Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# train_df[33:34]\ntrain_df[0:1]","metadata":{"execution":{"iopub.status.busy":"2024-02-18T14:51:44.770772Z","iopub.execute_input":"2024-02-18T14:51:44.771259Z","iopub.status.idle":"2024-02-18T14:51:44.789209Z","shell.execute_reply.started":"2024-02-18T14:51:44.771222Z","shell.execute_reply":"2024-02-18T14:51:44.788216Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"eeg_arr, spec_dict = get_array(1628180742, 353733, .0, .0)","metadata":{"execution":{"iopub.status.busy":"2024-02-18T14:52:01.148863Z","iopub.execute_input":"2024-02-18T14:52:01.149314Z","iopub.status.idle":"2024-02-18T14:52:01.156594Z","shell.execute_reply.started":"2024-02-18T14:52:01.149281Z","shell.execute_reply":"2024-02-18T14:52:01.155314Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"- 10 min. spectrograms for LL, RL, LP, RP.","metadata":{}},{"cell_type":"code","source":"import matplotlib.pyplot as plt\n\nfor k in spec_label:\n    plt.figure(figsize=(7,3))\n    plt.title(k)\n    plt.imshow(np.log(spec_dict[k]), cmap='jet', origin='lower')\n    plt.show()","metadata":{"execution":{"iopub.status.busy":"2024-02-18T15:26:08.368514Z","iopub.execute_input":"2024-02-18T15:26:08.368912Z","iopub.status.idle":"2024-02-18T15:26:10.165526Z","shell.execute_reply.started":"2024-02-18T15:26:08.368881Z","shell.execute_reply":"2024-02-18T15:26:10.164129Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"- Labels on 10 sec. EEG\n- Anterior-Posterior Bipolar Montage","metadata":{}},{"cell_type":"code","source":"chains = {\n    'LL': ['Fp1', 'F7', 'T3', 'T5', 'O1'],\n    'RR': ['Fp2', 'F8', 'T4', 'T6', 'O2'],\n    'LP': ['Fp1', 'F3', 'C3', 'P3', 'O1'],\n    'RP': ['Fp2', 'F4', 'C4', 'P4', 'O2'],\n    'C': ['Fz', 'Cz', 'Pz'],\n    'EKG': ['EKG']\n}\n\ndef plot_eeg(eeg, chain, window=10):\n    sep = 100\n    nx = eeg.shape[0]\n    c = nx//2\n    s, e = c-(window//2)*200, c+(window//2)*200\n    eeg_df = pd.DataFrame(eeg[s:e], columns=eeg_label)\n    X = np.linspace(-window//2, window//2, window*200)\n    yticklabels = []\n    plt.figure().set_figwidth(15)\n    ax = plt.gca()\n\n    for i in range(len(chain)-1):\n        a, b = chain[i], chain[i+1]\n        yticklabels.append(f'{a}-{b}')\n        ax.plot(X, eeg_df[a]-eeg_df[b]+((len(chain)-2-i)*sep), linewidth=0.5, color='black')\n    if len(chain) == 1:\n        yticklabels.append(f'{chain[0]}')\n        ax.plot(X, eeg_df[chain[0]], linewidth=0.5, color='black')\n\n    ax.set(\n        ylim=(-sep, (len(yticklabels))*sep),\n        yticks=np.arange(len(yticklabels))*sep,\n        yticklabels=yticklabels[::-1]\n    )\n    ax.set_xlabel('sec.')\n    plt.show()","metadata":{"execution":{"iopub.status.busy":"2024-02-18T15:25:54.737544Z","iopub.execute_input":"2024-02-18T15:25:54.737952Z","iopub.status.idle":"2024-02-18T15:25:54.753419Z","shell.execute_reply.started":"2024-02-18T15:25:54.737920Z","shell.execute_reply":"2024-02-18T15:25:54.752248Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"for k in chains.keys():\n    plot_eeg(eeg_arr, chains[k])","metadata":{"execution":{"iopub.status.busy":"2024-02-18T15:25:55.351496Z","iopub.execute_input":"2024-02-18T15:25:55.351929Z","iopub.status.idle":"2024-02-18T15:25:56.497629Z","shell.execute_reply.started":"2024-02-18T15:25:55.351895Z","shell.execute_reply":"2024-02-18T15:25:56.496644Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"## Preprocess\n\n- Data preprocess 방법은 공개되지 않았으나 보통 다음과 같이 사용한다.\n    - Low frequency filter (LFF): 1Hz\n    - High frequency filter (HFF): 70Hz\n    - notch filter: 60Hz (Europe: 50Hz)","metadata":{}}]}