{"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":30664,"isInternetEnabled":true,"language":"python","sourceType":"notebook","isGpuEnabled":false}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"markdown","source":"# MixUp aug in Albumentations","metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19"}},{"cell_type":"markdown","source":"In version [1.4.1](https://github.com/albumentations-team/albumentations/releases/tag/1.4.1), released at 4 March 2024 we added MixUp transform.\n\n(It will take some time for Kaggle docker to update to that version) \n\nHere is an example of how to apply it for this competition.\n\nUPD: in vetsion [1.4.2](https://github.com/albumentations-team/albumentations/releases/tag/1.4.2), released at 18 March 2024 we added an option to return mixing coefficient from MixUp application.","metadata":{}},{"cell_type":"code","source":"%matplotlib inline","metadata":{"execution":{"iopub.status.busy":"2024-03-19T00:33:31.287942Z","iopub.execute_input":"2024-03-19T00:33:31.289064Z","iopub.status.idle":"2024-03-19T00:33:31.325110Z","shell.execute_reply.started":"2024-03-19T00:33:31.288995Z","shell.execute_reply":"2024-03-19T00:33:31.323597Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"from pylab import *\nfrom pathlib import Path\nimport numpy as np\nimport pandas as pd","metadata":{"execution":{"iopub.status.busy":"2024-03-19T00:33:32.097329Z","iopub.execute_input":"2024-03-19T00:33:32.097835Z","iopub.status.idle":"2024-03-19T00:33:32.482692Z","shell.execute_reply.started":"2024-03-19T00:33:32.097796Z","shell.execute_reply":"2024-03-19T00:33:32.481639Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"import torch\nimport torchaudio","metadata":{"execution":{"iopub.status.busy":"2024-03-19T00:33:33.383455Z","iopub.execute_input":"2024-03-19T00:33:33.383916Z","iopub.status.idle":"2024-03-19T00:33:37.044825Z","shell.execute_reply.started":"2024-03-19T00:33:33.383886Z","shell.execute_reply":"2024-03-19T00:33:37.043692Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"!pip install -U albumentations","metadata":{"execution":{"iopub.status.busy":"2024-03-19T00:33:40.197717Z","iopub.execute_input":"2024-03-19T00:33:40.198492Z","iopub.status.idle":"2024-03-19T00:33:58.846454Z","shell.execute_reply.started":"2024-03-19T00:33:40.198449Z","shell.execute_reply":"2024-03-19T00:33:58.845494Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"import albumentations as A","metadata":{"execution":{"iopub.status.busy":"2024-03-19T00:34:02.441618Z","iopub.execute_input":"2024-03-19T00:34:02.442047Z","iopub.status.idle":"2024-03-19T00:34:03.457611Z","shell.execute_reply.started":"2024-03-19T00:34:02.441989Z","shell.execute_reply":"2024-03-19T00:34:03.456533Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"def spectrogram_from_eeg4(parquet_path: Path) -> np.ndarray:\n    FEATS = [\n        [\"Fp1\", \"F7\", \"T3\", \"T5\", \"O1\"],\n        [\"Fp1\", \"F3\", \"C3\", \"P3\", \"O1\"],\n        [\"Fp2\", \"F8\", \"T4\", \"T6\", \"O2\"],\n        [\"Fp2\", \"F4\", \"C4\", \"P4\", \"O2\"],\n    ]\n\n    # Load the entire EEG series\n    eeg = pd.read_parquet(parquet_path)\n\n    # Determine the maximum width based on the length of EEG data\n    max_width = len(eeg) // (200 // 2)  # Assuming hop_length = sample_rate / 2 for MelSpectrogram\n\n    # Variable to hold spectrogram\n    # Note: The width (second dimension) is now variable based on EEG length\n    img = np.zeros((128, max_width, 4), dtype=\"float32\")\n\n    for k in range(4):\n        cols = FEATS[k]\n\n        for kk in range(4):\n            # Compute pair differences\n            x = eeg[cols[kk]].to_numpy() - eeg[cols[kk + 1]].to_numpy()\n\n            # Fill NaNs\n            m = np.nanmean(x)\n            x = np.where(np.isnan(x), m, x)\n\n            # Convert to tensor and add a batch dimension\n            x_tensor = torch.tensor(x, dtype=torch.float32).unsqueeze(0)\n\n            # Adjust MelSpectrogram parameters based on EEG data length\n            mel_spectrogram = torchaudio.transforms.MelSpectrogram(\n                sample_rate=200,\n                n_fft=1024,\n                win_length=128,\n                hop_length=len(x) // max_width,  # Adjust based on the length of the data\n                n_mels=128,\n                f_min=0,\n                f_max=20,\n                power=2.0,\n            )\n\n            # Compute spectrogram\n            mel_spec_tensor = mel_spectrogram(x_tensor)\n\n            # Convert power spectrogram to dB scale\n            mel_spec_db_tensor = torchaudio.transforms.AmplitudeToDB(stype=\"power\")(mel_spec_tensor)\n\n            # Normalize and standardize the spectrogram\n            mel_spec_db_np = (mel_spec_db_tensor.numpy() + 40) / 40\n\n            # Ensure the spectrogram is not larger than allocated size\n            current_width = min(mel_spec_db_np.shape[2], max_width)\n            \n            img[:, :current_width, k] += mel_spec_db_np.squeeze()[:, :current_width]\n\n\n        # Average the 4 montage differences\n        img[:, :current_width, k] /= 4.0\n\n    # Reverse the frequency axis so low frequencies are at the bottom of the image\n    return img[::-1, :current_width, :]","metadata":{"execution":{"iopub.status.busy":"2024-03-19T00:34:04.841438Z","iopub.execute_input":"2024-03-19T00:34:04.841988Z","iopub.status.idle":"2024-03-19T00:34:04.855661Z","shell.execute_reply.started":"2024-03-19T00:34:04.841954Z","shell.execute_reply":"2024-03-19T00:34:04.853746Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"DATA_PATH = Path(\"/kaggle/input/hms-harmful-brain-activity-classification\")","metadata":{"execution":{"iopub.status.busy":"2024-03-19T00:34:05.708210Z","iopub.execute_input":"2024-03-19T00:34:05.708877Z","iopub.status.idle":"2024-03-19T00:34:05.713681Z","shell.execute_reply.started":"2024-03-19T00:34:05.708829Z","shell.execute_reply":"2024-03-19T00:34:05.712468Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"train_df = pd.read_csv(DATA_PATH / \"train.csv\")","metadata":{"execution":{"iopub.status.busy":"2024-03-19T00:34:06.644078Z","iopub.execute_input":"2024-03-19T00:34:06.644458Z","iopub.status.idle":"2024-03-19T00:34:06.889332Z","shell.execute_reply.started":"2024-03-19T00:34:06.644429Z","shell.execute_reply":"2024-03-19T00:34:06.887977Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"label_columns = train_df.filter(like=\"_vote\").columns.to_list()\n\ndata = train_df.groupby([\"eeg_id\"])[label_columns].sum()\nn = data.sum(axis=1)\nfor x in label_columns:\n    data[x] /= n","metadata":{"execution":{"iopub.status.busy":"2024-03-19T00:34:07.633099Z","iopub.execute_input":"2024-03-19T00:34:07.633499Z","iopub.status.idle":"2024-03-19T00:34:07.670850Z","shell.execute_reply.started":"2024-03-19T00:34:07.633461Z","shell.execute_reply":"2024-03-19T00:34:07.669781Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"data = data.astype(np.float32)\neeg_ids = data.index.to_numpy()","metadata":{"execution":{"iopub.status.busy":"2024-03-19T00:34:08.592134Z","iopub.execute_input":"2024-03-19T00:34:08.592502Z","iopub.status.idle":"2024-03-19T00:34:08.598446Z","shell.execute_reply.started":"2024-03-19T00:34:08.592473Z","shell.execute_reply":"2024-03-19T00:34:08.597226Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"## Define target image","metadata":{}},{"cell_type":"code","source":"img = spectrogram_from_eeg4(DATA_PATH / \"train_eegs\" / f\"{eeg_ids[0]}.parquet\")\nglobal_label = data.loc[eeg_ids[0]].to_numpy()","metadata":{"execution":{"iopub.status.busy":"2024-03-19T00:34:12.496622Z","iopub.execute_input":"2024-03-19T00:34:12.497001Z","iopub.status.idle":"2024-03-19T00:34:12.861162Z","shell.execute_reply.started":"2024-03-19T00:34:12.496973Z","shell.execute_reply":"2024-03-19T00:34:12.860239Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"img.shape, img.min(), img.max()","metadata":{"execution":{"iopub.status.busy":"2024-03-19T00:34:13.752819Z","iopub.execute_input":"2024-03-19T00:34:13.753218Z","iopub.status.idle":"2024-03-19T00:34:13.760777Z","shell.execute_reply.started":"2024-03-19T00:34:13.753186Z","shell.execute_reply":"2024-03-19T00:34:13.759917Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"print(\"Global label = \", global_label)\nplt.imshow(img[:, :, 0])","metadata":{"execution":{"iopub.status.busy":"2024-03-19T00:34:20.228108Z","iopub.execute_input":"2024-03-19T00:34:20.228509Z","iopub.status.idle":"2024-03-19T00:34:20.638449Z","shell.execute_reply.started":"2024-03-19T00:34:20.228435Z","shell.execute_reply":"2024-03-19T00:34:20.637363Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"If we use images in float32 format for MixUp they should be within [0,1] range","metadata":{}},{"cell_type":"code","source":"def normalize_image(image):\n    \"\"\"\n    Normalize to [0, 1]\n    \"\"\"\n    max_value = image.max()\n    min_value = image.min()\n    return (image - min_value) / (max_value - min_value)","metadata":{"execution":{"iopub.status.busy":"2024-03-19T00:34:23.500593Z","iopub.execute_input":"2024-03-19T00:34:23.501598Z","iopub.status.idle":"2024-03-19T00:34:23.507198Z","shell.execute_reply.started":"2024-03-19T00:34:23.501562Z","shell.execute_reply":"2024-03-19T00:34:23.506017Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"For mixing we need to define:\n* `reference_data` which is generator or iterator\n* `read_fn` function that takes elements of `reference_data` as input and returns dictionary of the type \n\n```python\n { \n    \"image\": np.ndarray,\n     \"mask\": Optional[np.ndarray],\n     \"global_label\": Optional[np.ndarray]\n}\n```\nand global_label is 1D vector representing label of the image. In the case of classification it is one hot representation of the target.\n\n\nIn `read_fn` we process images for mixing and we can have separate [Albumentations](https://albumentations.ai/) pipeline applied to it.","metadata":{"execution":{"iopub.status.busy":"2024-03-05T00:40:35.701176Z","iopub.execute_input":"2024-03-05T00:40:35.702010Z","iopub.status.idle":"2024-03-05T00:40:35.706247Z","shell.execute_reply.started":"2024-03-05T00:40:35.701960Z","shell.execute_reply":"2024-03-05T00:40:35.705184Z"}}},{"cell_type":"code","source":"target_height = 128\ntarget_width = 100","metadata":{"execution":{"iopub.status.busy":"2024-03-19T00:34:26.103461Z","iopub.execute_input":"2024-03-19T00:34:26.104081Z","iopub.status.idle":"2024-03-19T00:34:26.108810Z","shell.execute_reply.started":"2024-03-19T00:34:26.104048Z","shell.execute_reply":"2024-03-19T00:34:26.107503Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"transform_reference_data = A.Compose([A.RandomCrop(height=target_height, width=target_width, p=1)])","metadata":{"execution":{"iopub.status.busy":"2024-03-19T00:34:27.151255Z","iopub.execute_input":"2024-03-19T00:34:27.151641Z","iopub.status.idle":"2024-03-19T00:34:27.157213Z","shell.execute_reply.started":"2024-03-19T00:34:27.151613Z","shell.execute_reply":"2024-03-19T00:34:27.156121Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"eeg_ids[:10]","metadata":{"execution":{"iopub.status.busy":"2024-03-19T00:34:28.143289Z","iopub.execute_input":"2024-03-19T00:34:28.144319Z","iopub.status.idle":"2024-03-19T00:34:28.157475Z","shell.execute_reply.started":"2024-03-19T00:34:28.144271Z","shell.execute_reply":"2024-03-19T00:34:28.156360Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"def read_fn(eeg_id):\n    img = spectrogram_from_eeg4(DATA_PATH / \"train_eegs\" / f\"{eeg_id}.parquet\")\n    img = normalize_image(img)\n    \n    result_image = transform_reference_data(image=img)[\"image\"]    \n    global_label = data.loc[eeg_id].to_numpy()\n        \n    return {\"image\": result_image, \"global_label\": global_label}","metadata":{"execution":{"iopub.status.busy":"2024-03-19T00:34:30.680662Z","iopub.execute_input":"2024-03-19T00:34:30.681013Z","iopub.status.idle":"2024-03-19T00:34:30.687903Z","shell.execute_reply.started":"2024-03-19T00:34:30.680987Z","shell.execute_reply":"2024-03-19T00:34:30.686395Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"transform = A.Compose([A.RandomCrop(height=target_height, width=target_width, p=1), \n                       A.MixUp(reference_data=list(eeg_ids), read_fn=read_fn, alpha=0.4, p=1)])","metadata":{"execution":{"iopub.status.busy":"2024-03-19T00:35:20.438864Z","iopub.execute_input":"2024-03-19T00:35:20.439281Z","iopub.status.idle":"2024-03-19T00:35:20.446245Z","shell.execute_reply.started":"2024-03-19T00:35:20.439247Z","shell.execute_reply":"2024-03-19T00:35:20.445028Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"transformed = transform(image = normalize_image(img), global_label=global_label)\nprint(\"Global_label = \", transformed[\"global_label\"])\nprint(\"Mixing coefficient = \", transformed[\"mix_coef\"])\nplt.imshow(transformed[\"image\"][:, :, 0])","metadata":{"execution":{"iopub.status.busy":"2024-03-19T00:35:21.468878Z","iopub.execute_input":"2024-03-19T00:35:21.469766Z","iopub.status.idle":"2024-03-19T00:35:21.852988Z","shell.execute_reply.started":"2024-03-19T00:35:21.469724Z","shell.execute_reply":"2024-03-19T00:35:21.851796Z"},"trusted":true},"execution_count":null,"outputs":[]}]}