{"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":"nvidiaTeslaT4","dataSources":[{"sourceId":59093,"databundleVersionId":7469972,"sourceType":"competition"},{"sourceId":2799445,"sourceType":"datasetVersion","datasetId":1710071},{"sourceId":8095529,"sourceType":"datasetVersion","datasetId":4392176}],"isInternetEnabled":false,"language":"python","sourceType":"notebook","isGpuEnabled":true}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"code","source":"!pip install -q icecream --no-index --find-links=file:///kaggle/input/icecream/  ","metadata":{"execution":{"iopub.status.busy":"2024-04-12T12:58:05.835558Z","iopub.execute_input":"2024-04-12T12:58:05.835921Z","iopub.status.idle":"2024-04-12T12:58:18.856280Z","shell.execute_reply.started":"2024-04-12T12:58:05.835892Z","shell.execute_reply":"2024-04-12T12:58:18.855091Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"import numpy as np\nimport pandas as pd \nfrom icecream import ic\nimport cv2\nimport scipy\nfrom scipy.signal import butter, filtfilt, iirnotch, lfilter, firwin\nimport matplotlib.pyplot as plt\nimport torch\nfrom torch import nn\nimport torch.nn.functional as F\nfrom torch.utils.data import Dataset, DataLoader\nfrom torchvision.transforms import v2\nimport timm\nfrom tqdm.auto import tqdm\nfrom collections.abc import Iterable\nfrom IPython.display import display","metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","execution":{"iopub.status.busy":"2024-04-12T12:58:18.858942Z","iopub.execute_input":"2024-04-12T12:58:18.859606Z","iopub.status.idle":"2024-04-12T12:58:27.893054Z","shell.execute_reply.started":"2024-04-12T12:58:18.859566Z","shell.execute_reply":"2024-04-12T12:58:27.892069Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"MODEL_NAME = 'harmfull-brain-activity'\nMIN_VOTES = 10\nNAMES = ['LL','RL','LP','RP']\nEEG_COLS = ['Fp1', 'F3', 'C3', 'P3', 'F7', 'T3', 'T5', 'O1', 'Fz', 'Cz', 'Pz',\n            'Fp2', 'F4', 'C4', 'P4', 'F8', 'T4', 'T6', 'O2', 'EKG']\nOTHER_COLS = ['EKG', 'Fz', 'Cz', 'Pz']\nTARGETS = ['seizure_vote', 'lpd_vote', 'gpd_vote', 'lrda_vote', 'grda_vote', 'other_vote']\n\nclass FLAGS:\n  root = '../input/hms-harmful-brain-activity-classification'\n  model_dir = f'../input/{MODEL_NAME}-model'\n  backbone = 'efficientvit_b2'\n  use_specs = True\n  use_kaggle_specs = True\n  use_eeg_specs = True\n  # add raw eeg data to image\n  add_raw = True  \n  # weather add raw eeg data center 10s to image\n  raw_center = True\n  # gstft,gmel for torchaudio, dspec3.1-0-40 for scipy.signal.spectrogram\n  strategy = '2.2,8.2,5.1,7'\n  strategy2 = '8.2'\n  max_specs = 18\n  # for each channel the len is 40\n  spec_freq = 40\n  spec_time = 600\n  spec2_freq = 40\n  spec2_time = 300\n  kaggle_spec_clip = 8\n  norm_method = 'clip'\n  norm_clip = 18\n  # crop central 10s of kaggle spec\n  kaggle_crop = True \n  # make the cropped central 10s kaggle spec to 100 so combine original kaggle spec(300, 400) to get shape(400, 400) \n  kaggle_scale = 20.\n  center_crop = True\n  # lazy spec True means using torchaudio.stf or mel which using gpu while False means using scipy.signal.spectrogram which using cpu\n  # notice best PB single model is using scipy.signal.spectrogram while best LB model using torchaudio.stft, not much difference though \n  # but in general scipy.signal.spectrogram perform better, the PB is similar to my ensemble 20 models 0.2866 during the competition\n  # torchaudo.stft with LB 0.2321 PB 0.2905\n  # scipy.signal.spectrogram with LB 0.2337 PB 0.28716\n  lazy_spec = False \n  center_seconds = 10\n  eeg_fn = 'dspec3.1-0-40'\n  low_cut = 0.5\n  high_cut = 40.\n  band_order = 2\n  filter_method = '2'\n  # if False only use ceter 10_000 for each eeg_id\n  dynamic_eeg = True\n  dynamic_spec = True\n  oned_clip = 1024\n  oned_base = 1024\n  mheads = True\n  num_workers = 2\n  batch_size = 32\n  drop_rate = 0.1\n  tta = True\n  mode = 'test'\n  work_mode = 'test'","metadata":{"execution":{"iopub.status.busy":"2024-04-12T12:58:27.894207Z","iopub.execute_input":"2024-04-12T12:58:27.894492Z","iopub.status.idle":"2024-04-12T12:58:27.905671Z","shell.execute_reply.started":"2024-04-12T12:58:27.894469Z","shell.execute_reply":"2024-04-12T12:58:27.904533Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"test = pd.read_csv(f'{FLAGS.root}/test.csv')\ntest","metadata":{"execution":{"iopub.status.busy":"2024-04-12T12:58:27.907037Z","iopub.execute_input":"2024-04-12T12:58:27.907439Z","iopub.status.idle":"2024-04-12T12:58:27.937946Z","shell.execute_reply.started":"2024-04-12T12:58:27.907407Z","shell.execute_reply":"2024-04-12T12:58:27.937149Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"Util functions","metadata":{}},{"cell_type":"code","source":"def log_transform(x, exp_min=-4, exp_max=8, logarithm=None):\n  x = np.clip(x, np.exp(exp_min), np.exp(exp_max))\n  if logarithm == 2:\n    x = np.log2(x)\n  elif logarithm == 10:\n    x = np.log10(x)\n  else:\n    x = np.log(x)\n  return x\n\ndef log_normalize(x, clip, clip2=None, logarithm=None, eps=0.1):\n  if clip2 is None:\n    clip2 = clip\n  x = log_transform(x, -clip, clip2, logarithm)\n  x /= (max(clip, clip2) + eps)\n  return x\n\ndef resize(image, shape):\n  return cv2.resize(image, (shape[1], shape[0]), interpolation=cv2.INTER_AREA)\n\ndef center_crop(X, rate, keep_shape=False, scale=None, shift=0, sample_rate=1):\n  width = X.shape[1]\n  s = width * ((1 - rate) / 2.)\n  s -= shift * sample_rate\n  s = int(s)\n  e = s + int(width * rate)\n  X = X[:, s:e]\n  if keep_shape:\n    X = resize(X, (X.shape[0], width))\n  elif scale:\n    X = resize(X, (X.shape[0], int(width * rate * scale)))\n  return X\n\ndef normalize_image(x):\n  if not 'tf_' in FLAGS.backbone:\n    # for torch pretrained model, normalize from -1,1 to 0,1\n    x = (x + 1.) / 2.\n    \n  return x\n\ndef nan2mean(x):\n  m = np.nanmean(x)\n  if np.isnan(x).mean() < 1:\n    x = np.nan_to_num(x, nan=m)\n  else:\n    x[:] = 0\n  return x\n\ndef fixnan(x):\n  x = nan2mean(x)\n  return x\n\ndef norm(spec, method=None, clip=None):\n  if not method:\n    return spec\n  if method == 'none':\n    return spec\n  if method == 'db':\n    spec = librosa.power_to_db(spec, ref=np.max).astype(np.float32)\n    spec = (spec + 40) / 40\n  elif method == 'clip':\n    spec = log_normalize(spec, clip)\n  elif method == 'minmax':\n    min_ = spec.min()\n    max_ = spec.max()\n    spec = (spec - min_) / (max_ - min_)\n  return spec\n\nglobal_dict = {}\ndef Set(key, val):\n  global_dict[key] = val\ndef Get(key):\n  return global_dict.get(key, None)","metadata":{"execution":{"iopub.status.busy":"2024-04-12T12:58:27.940260Z","iopub.execute_input":"2024-04-12T12:58:27.940646Z","iopub.status.idle":"2024-04-12T12:58:27.958382Z","shell.execute_reply.started":"2024-04-12T12:58:27.940618Z","shell.execute_reply":"2024-04-12T12:58:27.957467Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"Singal filter","metadata":{}},{"cell_type":"code","source":"# https://www.kaggle.com/code/konstantinboyko/hms-resnet1d-gru-train-1-5-dataset\ndef butter_bandpass(lowcut, highcut, fs, order=5):\n  return butter(order, [lowcut, highcut], fs=fs, btype=\"band\")\n\ndef butter_bandpass_filter(data, lowcut, highcut, fs, order=5):\n  b, a = butter_bandpass(lowcut, highcut, fs, order=order)\n  y = lfilter(b, a, data)\n  return y\n\ndef filter(x, method=None, low_cut=None, high_cut=None, order=None, mode='test'):\n  if low_cut is None:\n    low_cut = FLAGS.low_cut\n    if low_cut == 0:\n      low_cut = 0.01\n    \n  if high_cut is None:\n    high_cut = FLAGS.high_cut\n    if high_cut == 100:\n      high_cut = 99.99\n    \n  if order is None:\n    order = FLAGS.band_order\n    \n  match method:\n    case '1':\n      nyquist_freq = 0.5 * 200\n      low_cut_freq_normalized = 0.7 / nyquist_freq\n      high_cut_freq_normalized = high_cut / nyquist_freq\n\n      # Bandpass and notch filter\n      bandpass_coefficients = butter(\n          order, [low_cut_freq_normalized, high_cut_freq_normalized], btype='band')\n      notch_coefficients = iirnotch(w0=60, Q=30, fs=200)\n      x = filtfilt(*notch_coefficients, x)\n      x = filtfilt(*bandpass_coefficients, x)\n    case '2':\n      x = butter_bandpass_filter(x, low_cut, high_cut, 200, order=order)    \n  return x","metadata":{"execution":{"iopub.status.busy":"2024-04-12T12:58:27.959615Z","iopub.execute_input":"2024-04-12T12:58:27.959916Z","iopub.status.idle":"2024-04-12T12:58:27.970331Z","shell.execute_reply.started":"2024-04-12T12:58:27.959893Z","shell.execute_reply":"2024-04-12T12:58:27.969447Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"Eeg to spec","metadata":{}},{"cell_type":"code","source":"def _get_spec(x, eeg_fn, freq_len, time_len, filter_method=None, lazy=False, mode='test'):\n  if lazy or FLAGS.lazy_spec:\n    if filter_method is None:\n      filter_method = FLAGS.filter_method or '2'\n    x = filter(x, filter_method)\n    x = np.expand_dims(x, 0).astype(np.float32)\n    assert x.shape[1] == 10_000, x.shape\n    return x\n  \n  if eeg_fn == '1d':\n    filter_method = FLAGS.filter_method or '2'\n    x = filter(x, filter_method, mode=mode)\n    x = np.clip(x, -FLAGS.oned_clip, FLAGS.oned_clip)\n    x /= FLAGS.oned_base\n    x = np.expand_dims(x, 0)\n    return x\n  \n  norm_method = FLAGS.norm_method\n  norm_clip = FLAGS.norm_clip\n\n  if '_' in eeg_fn:\n    eeg_fn, norm_clip = eeg_fn.split('_')\n    norm_clip = int(norm_clip)\n\n  min_freq = 0\n  max_freq = 100\n  if '-' in eeg_fn:\n    eeg_fn, min_freq, max_freq = eeg_fn.split('-')\n    min_freq, max_freq = float(min_freq), float(max_freq)\n    \n  low_cut = min_freq\n  hight_cut = max_freq\n\n  if eeg_fn == 'stft3.1.4':\n    x = filter(x, '2', mode=mode)\n    n_fft = (freq_len - 1) * 2\n    spec = librosa.stft(x,\n                        n_fft=n_fft,\n                        win_length=n_fft // 2,\n                        hop_length=len(x) // time_len)\n    spec = np.abs(spec)\n  elif eeg_fn == 'mel6':\n    spec = librosa.feature.melspectrogram(y=x,\n                                          sr=200,\n                                          hop_length=len(x) // time_len,\n                                          n_fft=2048,\n                                          n_mels=freq_len,\n                                          fmin=0,\n                                          fmax=20,\n                                          win_length=freq_len)\n  elif eeg_fn == 'dspec3.1':\n    # will run here\n    fs = 200\n    nperseg = 128\n    spec_time = Get('spec_time')\n    # noverlap = int(nperseg - 10_000 / spec_time)\n    # if 10_000 / (nperseg - noverlap) < spec_time:\n    #   noverlap += 1\n    match spec_time:\n      case 600:\n        # output is 618\n        noverlap = 112\n      case 300:\n        noverlap = 96\n      case 1000:\n        # output is 209 for 2000 input, notie 10000 / (128 - 118) / 5 = 200, but here if set 118 will output 188 TODO\n        noverlap = 119\n      case _:\n        raise ValueError(f'invalid spec_time {spec_time}')\n      \n    x = filter(x, '2', mode=mode)\n    rate = (max_freq - min_freq) / 100\n    freq_len_ = 100\n    nfft = (freq_len_ - 1) * 2 / rate\n    # ic(nfft)\n    frequencies, times, spec = scipy.signal.spectrogram(x,\n                                                        fs,\n                                                        nperseg=nperseg,\n                                                        noverlap=noverlap,\n                                                        nfft=nfft)    \n    n_freqs = 0\n    while (n_freqs < freq_len):\n      valid_freqs = (frequencies >= min_freq) & (frequencies <= max_freq)\n      n_freqs = valid_freqs.sum()\n      if max_freq < 100:\n        max_freq += 0.1\n      else:\n        min_freq -= 0.1\n    frequencies_filtered = frequencies[valid_freqs]\n    spec = spec[valid_freqs, :]\n    spec = spec[:freq_len_, :]\n    spec = resize(spec, (freq_len, spec.shape[1]))\n  else:\n    raise ValueError(f'Unknown eeg_fn {eeg_fn}')\n\n  spec = spec[:, :time_len]\n  spec = norm(spec, norm_method, norm_clip)\n  return spec","metadata":{"execution":{"iopub.status.busy":"2024-04-12T12:58:27.971695Z","iopub.execute_input":"2024-04-12T12:58:27.971995Z","iopub.status.idle":"2024-04-12T12:58:27.990178Z","shell.execute_reply.started":"2024-04-12T12:58:27.971948Z","shell.execute_reply":"2024-04-12T12:58:27.989322Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# https://www.learningeeg.com/montages-and-technical-components\ndef eeg2spec(eeg, eeg_fn, freq_len, time_len, eeg_strategy=0, eeg_channels=[], filter_method=None, lazy=False, mode='test'):\n  imgs = []\n  def _get_part(COLS):\n    # img = np.zeros((freq_len, time_len), dtype='float32')\n    img = None\n    COLS2 = COLS[1:]\n    COLS = COLS[:-1]\n    for kk in range(len(COLS)):\n      x = eeg[COLS[kk]].values - eeg[COLS2[kk]].values\n      x = fixnan(x)\n      spec = _get_spec(x, eeg_fn, freq_len, time_len, filter_method, lazy=lazy, mode=mode)\n      if img is None:\n        img = spec\n      else:\n        img += spec\n    img /= len(COLS)\n    imgs.append(img)\n    \n  def _get_part2(COLS):\n    COLS2 = COLS[1:]\n    COLS = COLS[:-1]\n    for kk in range(len(COLS)):\n      x = eeg[COLS[kk]].values - eeg[COLS2[kk]].values\n      x = fixnan(x)\n      spec = _get_spec(x, eeg_fn, freq_len, time_len, filter_method, lazy=lazy, mode=mode)\n      imgs.append(spec)\n      \n  def _get_part2_2(COLS):\n    COLS2 = COLS[1:]\n    COLS = COLS[:-1]\n    img = None\n    for kk in range(len(COLS)):\n      x = eeg[COLS[kk]].values - eeg[COLS2[kk]].values\n      x = fixnan(x)\n      spec = _get_spec(x, eeg_fn, freq_len, time_len, filter_method, lazy=lazy, mode=mode)\n      imgs.append(spec)\n      if img is None:\n        img = spec\n      else:\n        img += spec\n    img /= len(COLS)\n    imgs.append(img)\n      \n  def _get_part3(COLS):\n    COLS2 = COLS[1:]\n    COLS = COLS[:-1]\n    specs = {}\n    for kk in range(len(COLS)):\n      for col in [COLS[kk], COLS2[kk]]:\n        if not col in specs:\n          x = eeg[col].values\n          x = fixnan(x)\n          x = _get_spec(x, eeg_fn, freq_len, time_len, filter_method, lazy=lazy, mode=mode)\n          specs[col] = x\n      spec = specs[COLS[kk]] - specs[COLS2[kk]]\n      # to -1,1\n      spec /= 2.\n      imgs.append(spec)\n      \n  def _get_parts4(COLS1, COLS2):\n    for col1, col2 in zip(COLS1, COLS2):\n      x = eeg[col1].values - eeg[col2].values\n      x = fixnan(x)\n      spec = _get_spec(x, eeg_fn, freq_len, time_len, filter_method, lazy=lazy, mode=mode)\n      imgs.append(spec)\n  \n  if eeg_strategy == '0':\n    # 4象限 双香蕉，但是少 cz fz pz 中间区域象限\n    FEATS = [['Fp1', 'F7', 'T3', 'T5', 'O1'], \n             ['Fp2', 'F8', 'T4', 'T6', 'O2'],\n             ['Fp1', 'F3', 'C3', 'P3', 'O1'], \n             ['Fp2', 'F4', 'C4', 'P4', 'O2']]\n\n    for k in range(4):\n      img = np.zeros((freq_len, time_len), dtype='float32')\n      COLS = FEATS[k]\n      for kk in range(4):\n\n        # COMPUTE PAIR DIFFERENCES\n        x = eeg[COLS[kk]].values - eeg[COLS[kk + 1]].values\n        x = fixnan(x)\n        spec = _get_spec(x, eeg_fn, freq_len, time_len, filter_method, lazy=lazy, mode=mode)\n        img += spec\n\n      img /= 4.0\n      imgs.append(img)\n  elif eeg_strategy == '1':\n    # 直接encode各个channel 特别的encode 心电图EKG\n    if eeg_channels == ['all']:\n      eeg_channels = []\n    if eeg_channels == ['others']:\n      eeg_channels = OTHER_COLS\n    cols = eeg_channels or eeg.columns\n    # ic(cols)\n    for col in cols:\n      x = eeg[col].values\n      x = fixnan(x)\n      spec = _get_spec(x, eeg_fn, freq_len, time_len, filter_method, lazy=lazy, mode=mode)\n      imgs.append(spec)\n  elif eeg_strategy == '2':\n    # 5象限 完整双香蕉\n    FEATS = [['Fp1', 'F7', 'T3', 'T5', 'O1'], \n             ['Fp2', 'F8', 'T4', 'T6', 'O2'],\n             ['Fp1', 'F3', 'C3', 'P3', 'O1'], \n             ['Fp2', 'F4', 'C4', 'P4', 'O2']]\n\n    for k in range(4):\n      img = None\n      COLS = FEATS[k]\n      for kk in range(4):\n\n        # COMPUTE PAIR DIFFERENCES\n        x = eeg[COLS[kk]].values - eeg[COLS[kk + 1]].values\n        x = fixnan(x)\n\n        spec = _get_spec(x, eeg_fn, freq_len, time_len, filter_method, lazy=lazy, mode=mode)\n        if img is None:\n          img = spec\n        else:\n          img += spec\n\n      img /= 4.\n      imgs.append(img)\n\n    COLS = ['Fz', 'Cz', 'Pz']\n    img = None\n    for kk in range(2):\n      x = eeg[COLS[kk]].values - eeg[COLS[kk + 1]].values\n      x = fixnan(x)\n      spec = _get_spec(x, eeg_fn, freq_len, time_len, filter_method, lazy=lazy, mode=mode)\n      if img is None:\n        img = spec\n      else:\n        img += spec\n    img /= 2.\n    imgs.append(img)\n  elif eeg_strategy == '2.1':\n    # 5象限 完整双香蕉,顺序按照标准...\n    FEATS = [\n             ['Fp1', 'F7', 'T3', 'T5', 'O1'],\n             ['Fp1', 'F3', 'C3', 'P3', 'O1'], \n             ['Fz', 'Cz', 'Pz'], \n             ['Fp2', 'F4', 'C4', 'P4', 'O2'],\n             ['Fp2', 'F8', 'T4', 'T6', 'O2'],\n             ]      \n    for COLS in FEATS:\n      _get_part(COLS)\n  elif eeg_strategy == '2.1.1':\n    FEATS = [\n          ['Fp1', 'F7', 'T3', 'T5', 'O1'],\n          ['Fp1', 'F3', 'C3', 'P3', 'O1'], \n          ['Fz', 'Cz', 'Pz'], \n          ['Fp2', 'F4', 'C4', 'P4', 'O2'],\n          ['Fp2', 'F8', 'T4', 'T6', 'O2'],\n          ]      \n    for COLS in FEATS:\n      _get_part(COLS)\n    \n    x = eeg['EKG'].values  \n    x = fixnan(x)\n    spec = _get_spec(x, eeg_fn, freq_len, time_len, filter_method, lazy=lazy, mode=mode)\n    imgs.append(spec)   \n  elif eeg_strategy == '2.2':\n    # 纵向双香蕉 输出18\n    FEATS = [\n          ['Fp1', 'F7', 'T3', 'T5', 'O1'],\n          ['Fp1', 'F3', 'C3', 'P3', 'O1'], \n          ['Fz', 'Cz', 'Pz'], \n          ['Fp2', 'F4', 'C4', 'P4', 'O2'],\n          ['Fp2', 'F8', 'T4', 'T6', 'O2'],\n          ]      \n    for COLS in FEATS:\n      _get_part2(COLS) \n  elif eeg_strategy == '2.2.x':\n    # 纵向双香蕉 输出19\n    FEATS = [\n          ['Fp1', 'F7', 'T3', 'T5', 'O1'],\n          ['Fp1', 'F3', 'C3', 'P3', 'O1'], \n          ['Fz', 'Cz', 'Pz'], \n          ['Fp2', 'F4', 'C4', 'P4', 'O2'],\n          ['Fp2', 'F8', 'T4', 'T6', 'O2'],\n          ]      \n    for COLS in FEATS:\n      _get_part2_2(COLS) \n  elif eeg_strategy == '2.2.0':\n    # 纵向双香蕉 no Fz Cz Pz\n    FEATS = [\n          ['Fp1', 'F7', 'T3', 'T5', 'O1'],\n          ['Fp1', 'F3', 'C3', 'P3', 'O1'], \n          ['Fp2', 'F4', 'C4', 'P4', 'O2'],\n          ['Fp2', 'F8', 'T4', 'T6', 'O2'],\n          ]      \n    for COLS in FEATS:\n      _get_part2(COLS) \n  elif eeg_strategy == '2.2.1':\n    FEATS = [\n          ['Fp1', 'F7', 'T3', 'T5', 'O1'],\n          ['Fp1', 'F3', 'C3', 'P3', 'O1'], \n          ['Fz', 'Cz', 'Pz'], \n          ['Fp2', 'F4', 'C4', 'P4', 'O2'],\n          ['Fp2', 'F8', 'T4', 'T6', 'O2'],\n          ]      \n    for COLS in FEATS:\n      _get_part2(COLS) \n      \n    x = eeg['EKG'].values  \n    x = fixnan(x)\n    spec = _get_spec(x, eeg_fn, freq_len, time_len, filter_method, lazy=lazy, mode=mode)\n    imgs.append(spec)   \n  elif eeg_strategy == '2.2.2':\n    # 单级模式 相邻求差 类似双香蕉 但是有一点差异... \n    COLS = ['Fp1', 'F7', 'T3', 'T5', 'O1', 'P3', 'C3', 'F3', 'Fz', 'Fp2', 'F8', 'T4', 'T6', 'O2', 'P4', 'C4', 'F4', 'Cz', 'Pz']\n    _get_part2(COLS)\n  elif eeg_strategy == '2.3':\n    FEATS = [\n          ['Fp1', 'F7', 'T3', 'T5', 'O1'],\n          ['Fp1', 'F3', 'C3', 'P3', 'O1'], \n          ['Fz', 'Cz', 'Pz'], \n          ['Fp2', 'F4', 'C4', 'P4', 'O2'],\n          ['Fp2', 'F8', 'T4', 'T6', 'O2'],\n          ]      \n    for COLS in FEATS:\n      _get_part3(COLS) \n  elif eeg_strategy == '3':\n    # 圆环模式 输出1个\n    COLS = ['Fp1', 'F7', 'T3', 'T5', 'O1', 'O2', 'T6', 'T4', 'F8', 'Fp2']\n    COLS2 = COLS[1:] + [COLS[0]]\n    img = np.zeros((freq_len, time_len), dtype='float32')\n    for kk in range(len(COLS)):\n      x = eeg[COLS[kk]].values - eeg[COLS2[kk]].values\n      x = fixnan(x)\n      spec = _get_spec(x, eeg_fn, freq_len, time_len, filter_method, lazy=lazy, mode=mode)\n      img += spec\n    img /= len(COLS)\n    imgs.append(img)\n  elif eeg_strategy == '4':\n    # 平均模式 输出3个\n    # 去掉最后的ekg?\n    # for avg\n    COLS = EEG_COLS[:-1]\n    v = eeg[COLS].values\n    v = np.nanmean(v, axis=1)\n    v = np.nan_to_num(v, nan=0.)\n    \n    def _get_part(COLS):\n      img = np.zeros((freq_len, time_len), dtype='float32')\n      for kk in range(len(COLS)):\n        x = eeg[COLS[kk]].values - v\n        x = fixnan(x)\n        spec = _get_spec(x, eeg_fn, freq_len, time_len, filter_method, lazy=lazy, mode=mode)\n        img += spec\n      img /= len(COLS)\n      imgs.append(img)\n\n    # left\n    COLS = ['Fp1', 'F7', 'T3', 'T5', 'O1', 'F3', 'C3', 'P3']\n    _get_part(COLS)\n    # center\n    COLS = ['Fz', 'Cz', 'Pz']\n    _get_part(COLS)\n    # right\n    COLS = ['Fp2', 'F8', 'T4', 'T6', 'O2', 'F4', 'C4', 'P4']\n    _get_part(COLS)\n  elif eeg_strategy == '4.x':\n    # 平均模式 输出3个\n    # 去掉最后的ekg?\n    # for avg\n    COLS = EEG_COLS[:-1]\n    v = eeg[COLS].values\n    v = np.nanmean(v, axis=1)\n    v = np.nan_to_num(v, nan=0.)\n\n    def _get_part(COLS):\n      for kk in range(len(COLS)):\n        x = eeg[COLS[kk]].values - v\n        x = fixnan(x)\n        spec = _get_spec(x, eeg_fn, freq_len, time_len, filter_method, lazy=lazy, mode=mode)\n        imgs.append(spec)\n        \n    # left\n    COLS = ['Fp1', 'F7', 'T3', 'T5', 'O1', 'F3', 'C3', 'P3']\n    _get_part(COLS)\n    # center\n    COLS = ['Fz', 'Cz', 'Pz']\n    _get_part(COLS)\n    # right\n    COLS = ['Fp2', 'F8', 'T4', 'T6', 'O2', 'F4', 'C4', 'P4']\n    _get_part(COLS)\n  elif eeg_strategy == '4.y':\n    # 平均模式 输出3个\n    # 去掉最后的ekg?\n    # for avg\n    COLS = EEG_COLS[:-1]\n    v = eeg[COLS].values\n    v = np.nanmean(v, axis=1)\n    v = np.nan_to_num(v, nan=0.)\n    \n    def _get_part(COLS):\n      img = None\n      for kk in range(len(COLS)):\n        x = eeg[COLS[kk]].values - v\n        x = fixnan(x)\n        spec = _get_spec(x, eeg_fn, freq_len, time_len, filter_method, lazy=lazy, mode=mode)\n        if img is None:\n          img = spec\n        else:\n          img += spec\n      img /= len(COLS)\n      imgs.append(img)\n      \n    FEATS = [\n          ['Fp1', 'F7', 'T3', 'T5', 'O1'],\n          ['Fp1', 'F3', 'C3', 'P3', 'O1'], \n          ['Fz', 'Cz', 'Pz'], \n          ['Fp2', 'F4', 'C4', 'P4', 'O2'],\n          ['Fp2', 'F8', 'T4', 'T6', 'O2'],\n          ]     \n\n    for COLS in FEATS:\n      _get_part(COLS)\n  elif eeg_strategy == '4.1':\n    # 平均模式 输出3个, 中心取Cz 而不是平均\n    # 去掉最后的ekg?\n    # for avg\n    v = eeg['Cz'].values\n\n    def _get_part(COLS):\n      img = np.zeros((freq_len, time_len), dtype='float32')\n      for kk in range(len(COLS)):\n        x = eeg[COLS[kk]].values - v\n        x = fixnan(x)\n        spec = _get_spec(x, eeg_fn, freq_len, time_len, filter_method, lazy=lazy, mode=mode)\n        img += spec\n      img /= len(COLS)\n      imgs.append(img)\n        \n    # left\n    COLS = ['Fp1', 'F7', 'T3', 'T5', 'O1', 'F3', 'C3', 'P3']\n    _get_part(COLS)\n    # center\n    COLS = ['Fz', 'Cz', 'Pz']\n    _get_part(COLS)\n    # right\n    COLS = ['Fp2', 'F8', 'T4', 'T6', 'O2', 'F4', 'C4', 'P4']\n    _get_part(COLS)\n  elif eeg_strategy == '4.1.x':\n    # 平均模式 输出3个, 中心取Cz 而不是平均\n    # 去掉最后的ekg?\n    # for avg\n    v = eeg['Cz'].values\n    \n    def _get_part(COLS):\n      for kk in range(len(COLS)):\n        x = eeg[COLS[kk]].values - v\n        x = fixnan(x)\n        spec = _get_spec(x, eeg_fn, freq_len, time_len, filter_method, lazy=lazy, mode=mode)\n        imgs.append(spec)\n\n    # left\n    COLS = ['Fp1', 'F7', 'T3', 'T5', 'O1', 'F3', 'C3', 'P3']\n    _get_part(COLS)\n    # center\n    COLS = ['Fz', 'Cz', 'Pz']\n    _get_part(COLS)\n    # right\n    COLS = ['Fp2', 'F8', 'T4', 'T6', 'O2', 'F4', 'C4', 'P4']\n    _get_part(COLS)\n  elif eeg_strategy == '5':\n    # 横向模式 输出5个\n    COLS = ['Fp1', 'Fp2']\n    _get_part(COLS)\n    COLS = ['F7', 'F3', 'Fz', 'F4', 'F8']\n    _get_part(COLS)\n    COLS = ['T3', 'C3', 'Cz', 'C4', 'T4']\n    _get_part(COLS)\n    COLS = ['T5', 'P3', 'Pz', 'P4', 'T6']\n    _get_part(COLS)\n    COLS = ['O1', 'O2']\n    _get_part(COLS)\n  elif eeg_strategy == '5.1':\n    # 横向模式输出19\n    FEATS = [\n          ['Fp1', 'Fp2'],\n          ['F7', 'F3', 'Fz', 'F4', 'F8'], \n          ['T3', 'C3', 'Cz', 'C4', 'T4'], \n          ['T5', 'P3', 'Pz', 'P4', 'T6'],\n          ['O1', 'O2'],\n          ]      \n    for COLS in FEATS:\n      _get_part2(COLS) \n  elif eeg_strategy == '6':\n    # 5 and 3也就是 横向模式 + 圆环模式\n    COLS = ['Fp1', 'Fp2']\n    _get_part(COLS)\n    COLS = ['F7', 'F3', 'Fz', 'F4', 'F8']\n    _get_part(COLS)\n    COLS = ['T3', 'C3', 'Cz', 'C4', 'T4']\n    _get_part(COLS)\n    COLS = ['T5', 'P3', 'Pz', 'P4', 'T6']\n    _get_part(COLS)\n    COLS = ['O1', 'O2']\n    _get_part(COLS)\n    COLS = ['Fp1', 'F7', 'T3', 'T5', 'O1', 'O2', 'T6', 'T4', 'F8', 'Fp2']\n    COLS2 = COLS[1:] + [COLS[0]]\n    img = np.zeros((freq_len, time_len), dtype='float32')\n    for kk in range(len(COLS)):\n      x = eeg[COLS[kk]].values - eeg[COLS2[kk]].values\n      x = fixnan(x)\n      spec = _get_spec(x, eeg_fn, freq_len, time_len, filter_method, lazy=lazy, mode=mode)\n      img += spec\n    img /= len(COLS)\n    imgs.append(img)\n  elif eeg_strategy == '7':\n    # 单独encode每个channel 但是按双香蕉顺序 \n    COLS = ['Fp1', 'F7', 'T3', 'T5', 'O1', 'P3', 'C3', 'F3', 'Fz', 'Fp2', 'F8', 'T4', 'T6', 'O2', 'P4', 'C4', 'F4', 'Cz', 'Pz']\n    assert len(COLS) == 19\n    assert len(set(COLS)) == 19\n    for col in COLS:\n      x = eeg[col].values\n      x = fixnan(x)\n      spec = _get_spec(x, eeg_fn, freq_len, time_len, filter_method, lazy=lazy, mode=mode)\n      imgs.append(spec)\n  elif eeg_strategy == '8':\n    # 参考模式 输出18个\n    COLS = ['F7', 'T3', 'T5', 'Fp1', 'F3', 'C3', 'P3', 'O1', 'Fz', 'Pz', 'Fp2', 'F4', 'C4', 'P4', 'O2', 'F8', 'T4', 'T6']\n    assert len(COLS) == 18\n    v = eeg['Cz'].values\n    for col in COLS:\n      x = eeg[col].values - v\n      x = fixnan(x)\n      spec = _get_spec(x, eeg_fn, freq_len, time_len, filter_method, lazy=lazy, mode=mode)\n      imgs.append(spec)\n  elif eeg_strategy == '8.1':\n    # 参考模式 输出18个\n    COLS = ['Fp1', 'F7', 'T3', 'T5', 'O1', 'P3', 'C3', 'F3', 'Fz', 'Fp2', 'F8', 'T4', 'T6', 'O2', 'P4', 'C4', 'F4', 'Pz']\n    assert len(COLS) == 18\n    v = eeg['Cz'].values\n    for col in COLS:\n      x = eeg[col].values - v\n      x = fixnan(x)\n      spec = _get_spec(x, eeg_fn, freq_len, time_len, filter_method, lazy=lazy, mode=mode)\n      imgs.append(spec)\n  elif eeg_strategy == '8.2':\n    # 参考模式 输出18个\n    COLS = ['Fp1', 'F7', 'T3', 'T5', 'O1', 'P3', 'C3', 'F3', 'Fz', 'Fp2', 'F8', 'T4', 'T6', 'O2', 'P4', 'C4', 'F4', 'Pz']\n    assert len(COLS) == 18\n    \n    COLS2 = EEG_COLS[:-1]\n    v = eeg[COLS2].values\n    v = np.nanmean(v, axis=1)\n    v = np.nan_to_num(v, nan=0.)\n    \n    for col in COLS:\n      x = eeg[col].values - v\n      x = fixnan(x)\n      spec = _get_spec(x, eeg_fn, freq_len, time_len, filter_method, lazy=lazy, mode=mode)\n      imgs.append(spec)\n  elif eeg_strategy == '8.3':\n    # 参考模式 输出19个\n    COLS = ['Fp1', 'F7', 'T3', 'T5', 'O1', 'P3', 'C3', 'F3', 'Fz', 'Fp2', 'F8', 'T4', 'T6', 'O2', 'P4', 'C4', 'F4', 'Pz']\n    COLS += ['EKG']\n    assert len(COLS) == 19\n    \n    COLS2 = EEG_COLS[:-1]\n    v = eeg[COLS2].values\n    v = np.nanmean(v, axis=1)\n    v = np.nan_to_num(v, nan=0.)\n    \n    for col in COLS:\n      x = eeg[col].values - v\n      x = fixnan(x)\n      spec = _get_spec(x, eeg_fn, freq_len, time_len, filter_method, lazy=lazy, mode=mode)\n      imgs.append(spec)\n  elif eeg_strategy == '1d':\n    # 最初的8个减法特征 \n    # https://www.kaggle.com/code/medali1992/hms-resnet1d-gru-train?scriptVersionId=163575181\n    _get_parts4(['Fp1', 'T3', 'Fp1', 'C3', 'Fp2', 'C4', 'Fp2', 'T4'], \n                ['T3', 'O1', 'C3', 'O1', 'C4', 'O2', 'T4', 'O2'])\n  elif eeg_strategy == '1d.1':\n    map_features = [\n        (\"Fp1\", \"T3\"),\n        (\"T3\", \"O1\"),\n        (\"Fp1\", \"C3\"),\n        (\"C3\", \"O1\"),\n        (\"Fp2\", \"C4\"),\n        (\"C4\", \"O2\"),\n        (\"Fp2\", \"T4\"),\n        (\"T4\", \"O2\"),\n        #('Fz', 'Cz'), ('Cz', 'Pz'),\n        #'F7', 'F3', 'Cz', 'P4', 'T6'\n        #'F8', 'F4', 'Cz', 'P3', 'T5'\n        ('F7', 'F3'),\n        ('F3', 'Cz'),\n        ('Cz', 'P4'),\n        ('P4', 'T6'),\n        ('F8', 'F4'),\n        ('F4', 'Cz'),\n        ('Cz', 'P3'),\n        ('P3', 'T5'),\n    ]\n    # _get_parts4(['Fp1', 'T3', 'Fp1', 'C3', 'Fp2', 'C4', 'Fp2', 'T4', 'F7', 'F3', 'Cz', 'P4', 'F8', 'F4', 'Cz', 'P3'], \n    #             ['T3', 'O1', 'C3', 'O1', 'C4', 'O2', 'T4', 'O2', 'F3', 'Cz', 'P4', 'T6', 'F4', 'Cz', 'P3', 'T5'])\n    # _get_parts4([x[0] for x in map_features], [x[1] for x in map_features])\n    _get_parts4(*zip(*map_features))\n  else:\n    raise ValueError(f'Unknown eeg_strategy {eeg_strategy}')\n\n  img = np.concatenate(imgs, axis=0)\n  return img","metadata":{"execution":{"iopub.status.busy":"2024-04-12T12:58:27.991380Z","iopub.execute_input":"2024-04-12T12:58:27.991692Z","iopub.status.idle":"2024-04-12T12:58:28.246500Z","shell.execute_reply.started":"2024-04-12T12:58:27.991668Z","shell.execute_reply":"2024-04-12T12:58:28.245359Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"EegBuilder","metadata":{}},{"cell_type":"code","source":"def spectrogram_from_eeg(eeg, strategy, eeg_fn, spec_freq, spec_time, center=False, \n                         filter_method=None, lazy=False, mode='test'):\n  if not FLAGS.dynamic_spec:\n    # just take middel 10_000 for each eeg id\n    middle = (len(eeg) - 10_000) // 2\n    eeg = eeg.iloc[middle:middle + 10_000]\n\n  rate = len(eeg) / 10_000\n  Set('spec_time', spec_time)\n  freq_len, time_len = spec_freq, int(spec_time * rate)\n  \n  if center:\n    Set('spec_time', spec_time * 5)\n    # from 50s to 10s\n    eeg = eeg.iloc[4000:6000]\n  \n  img = eeg2spec(eeg,\n                 eeg_fn,\n                 freq_len,\n                 time_len,\n                 eeg_strategy=strategy,\n                 eeg_channels=[],\n                 filter_method=filter_method,\n                 lazy=lazy,\n                 mode=mode)\n  return img","metadata":{"execution":{"iopub.status.busy":"2024-04-12T12:58:28.247675Z","iopub.execute_input":"2024-04-12T12:58:28.247970Z","iopub.status.idle":"2024-04-12T12:58:28.258682Z","shell.execute_reply.started":"2024-04-12T12:58:28.247946Z","shell.execute_reply":"2024-04-12T12:58:28.257738Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"def load_eeg_spectrograms(strategy,\n                          eeg_fn,\n                          spec_freq,\n                          spec_time,\n                          eeg_ids=None,\n                          eeg_offsets_map=None,\n                          mode='train'):\n  if ',' in strategy or ',' in eeg_fn:\n    strategies = strategy.split(',')\n    eeg_fns = eeg_fn.split(',')\n    if len(strategies) == 1:\n      strategies = strategies * len(eeg_fns)\n    elif len(eeg_fns) == 1:\n      eeg_fns = eeg_fns * len(strategies)\n    if FLAGS.add_raw and (not FLAGS.lazy_spec):\n      strategies, eeg_fns = strategies[:1], eeg_fns[:1]\n    for i, (strategy, eeg_fn) in enumerate(zip(strategies, eeg_fns)):\n      if i == 0:\n        eeg_specs = load_eeg_spectrograms(strategy, eeg_fn, spec_freq,\n                                          spec_time, eeg_ids, eeg_offsets_map,\n                                          mode)\n        ks = list(eeg_specs.keys())\n      else:\n        eeg_specs2 = load_eeg_spectrograms(strategy, eeg_fn, spec_freq,\n                                           spec_time, eeg_ids, eeg_offsets_map,\n                                           mode)\n        for k in tqdm(ks, desc='merge eegs', leave=False):\n          eeg_specs[k] = np.concatenate([eeg_specs[k], eeg_specs2[k]], axis=0)\n          del eeg_specs2[k]\n    return eeg_specs\n\n    eeg_specs = convert(eeg_ids, eeg_offsets_map, strategy, eeg_fn, spec_freq,\n                        spec_time, mode)\n\n    return eeg_specs\n\n\n  return eeg_specs\n\n\ndef convert(eeg_ids,\n            eeg_offsets_map,\n            strategy,\n            eeg_fn,\n            spec_freq,\n            spec_time,\n            mode='train'):\n  paths_eegs = glob.glob(f'{FLAGS.root}/{mode}_eegs/*.parquet')\n  counter = 0\n\n  def deal(eeg_id, mode):\n    parquet_path = f'{FLAGS.root}/{mode}_eegs/{eeg_id}.parquet'\n    eeg = pd.read_parquet(parquet_path)\n    return spectrogram_from_eeg(eeg, strategy, eeg_fn, spec_freq, spec_time)\n\n  eeg_spec_list = gezi.prun_list(lambda x: deal(x, mode=mode),\n                                 eeg_ids,\n                                 num_workers=FLAGS.pdl_workers)\n  eeg_specs = dict(zip(eeg_ids, eeg_spec_list))\n  return eeg_specs\n\n\ndef create_image(spec):\n  if len(spec.shape) == 2:\n    image = np.stack([spec, spec, spec], axis=-3)\n  else:\n    image = np.transpose(spec, (2, 0, 1))\n  return image\n\n\nclass BaseEEGBuilder():\n\n  def __init__(self):\n    self.eeg_specs = {}\n    self.eegs = {}\n    self.eeg_offsets_map = {}\n    self.eeg_ids = None\n    self.mode = None\n\n  def load_eeg_specs(self, mode):\n    if self.eeg_specs:\n      return self.eeg_specs\n\n    self.eeg_specs = load_eeg_spectrograms(FLAGS.eeg_strategy, FLAGS.eeg_fn,\n                                           FLAGS.spec_freq, FLAGS.spec_time,\n                                           self.eeg_ids, self.eeg_offsets_map,\n                                           mode)\n    ic(len(self.eeg_specs))\n    return self.eeg_specs\n  \n  def _get_eeg(self, row, idx, mode='test'):\n    shift = 0\n    if self.eegs:\n      eeg = self.eegs[row.eeg_id]\n    else:\n      mode_ = mode\n      if mode_ not in ['train', 'test']:\n        mode_ = 'train'\n      eeg = pd.read_parquet(f'{FLAGS.root}/{mode_}_eegs/{row.eeg_id}.parquet')\n   \n    if mode == 'test':\n      start = 0\n    else:\n      start = int(row.eeg_offset * 200)\n    \n    ## just take middel\n    if mode == 'train' and (not FLAGS.dynamic_eeg):\n      start = (len(eeg) - 10_000) // 2\n    \n    eeg = eeg.iloc[start:start + 10_000]\n    assert len(eeg) == 10_000, f'{start} {len(eeg)}'\n    return eeg, shift\n\n  def _gen_spec(self,\n                row,\n                idx,\n                strategy,\n                eeg_fn,\n                freq_len,\n                time_len,\n                filter_method=None,\n                lazy=False,\n                center=False,\n                mode='test'):\n    eeg, shift = self._get_eeg(row, idx, mode)\n    strategies = strategy.split(',')\n    eeg_fns = eeg_fn.split(',')\n    if len(eeg_fns) == 1:\n      eeg_fns = eeg_fns * len(strategies)\n      \n    if FLAGS.add_raw and (not FLAGS.lazy_spec) and (not lazy):\n      strategies, eeg_fns = strategies[:1], eeg_fns[:1]\n    specs = [\n        spectrogram_from_eeg(eeg, strategy, eeg_fn, freq_len, time_len, center, \n                             filter_method=filter_method, lazy=lazy, mode=mode)\n        for strategy, eeg_fn in zip(strategies, eeg_fns)\n    ]\n    spec = np.concatenate(specs, axis=0)\n    # ic(spec.min(), spec.max(), spec.mean(), spec.std())\n    return spec\n\n  def get_eeg_specs(self, row, idx=0, mode='test'):\n    X = self._gen_spec(row, idx, FLAGS.strategy, FLAGS.eeg_fn,\n                        FLAGS.spec_freq, FLAGS.spec_time, mode=mode)\n\n    return [X]\n\n  def merge_specs(self, kaggle_spec, eeg_spec_list, mode='test'):\n    image = None\n    if not eeg_spec_list:\n      spec = create_image(kaggle_spec)\n      if not FLAGS.lazy_spec:\n        return spec\n      else:\n        return {'kaggle': kaggle_spec}\n    eeg_spec = np.concatenate(eeg_spec_list, axis=0)\n    if kaggle_spec is None:\n      spec = create_image(eeg_spec)\n      # ic(spec.min(), spec.max(), spec.mean(), spec.std())\n      if not FLAGS.lazy_spec:\n        return spec\n      else:\n        return {'eeg': eeg_spec}\n\n    if FLAGS.lazy_spec:\n      return {'kaggle': kaggle_spec, 'eeg': eeg_spec}\n\n    width = eeg_spec.shape[1]\n    if kaggle_spec.shape[1] < width:\n      rate = FLAGS.center_seconds / 50.\n      shift = 0 \n      X = center_crop(eeg_spec, rate, shift=shift, sample_rate=200)\n      cut_shape = (kaggle_spec.shape[0],\n                    eeg_spec.shape[1] - kaggle_spec.shape[1])\n      if X.shape != cut_shape:\n        X = resize(X, cut_shape)\n      kaggle_spec = np.concatenate([kaggle_spec, X], axis=1)\n      assert kaggle_spec.shape[1] == width\n\n    width = max(kaggle_spec.shape[1], eeg_spec.shape[1])\n    if width > kaggle_spec.shape[1]:\n      kaggle_spec = resize(kaggle_spec, (kaggle_spec.shape[0], width))\n    if width > eeg_spec.shape[1]:\n      eeg_spec = resize(eeg_spec, (eeg_spec.shape[0], width))\n\n    # concat on height\n    spec = np.concatenate([kaggle_spec, eeg_spec], axis=0)\n    image = create_image(spec)\n    return image\n\nclass EEGBuilder(BaseEEGBuilder):\n  def __init__(self):\n    super().__init__()\n    self.eeg_specs2 = {}\n\n  def load_eeg_specs(self, mode):\n    if self.eeg_specs:\n      return self.eeg_specs\n    # 5 * FLAGS.spec_freq, FLAGS.spec_time\n    self.eeg_specs = load_eeg_spectrograms(FLAGS.eeg_strategy, FLAGS.eeg_fn, FLAGS.spec_freq, FLAGS.spec_time, \n                                           self.eeg_ids, self.eeg_offsets_map, mode)\n    # 5 * FLAGS.spec_freq, FLAGS.spec_time\n    eeg_fn = FLAGS.eeg_fn \n    if eeg_fn == 'spec6':\n      eeg_fn = 'spec4'\n    # by default using 5\n    self.eeg_specs2 = load_eeg_spectrograms(FLAGS.eeg_strategy2, eeg_fn, FLAGS.spec2_freq, FLAGS.spec2_time, \n                                            self.eeg_ids, self.eeg_offsets_map, mode)\n    ic(len(self.eeg_specs))\n    return self.eeg_specs\n  \n      \n  def get_eeg_specs(self, row, idx=0, mode='test'):\n    X = self._gen_spec(row, idx, FLAGS.strategy, FLAGS.eeg_fn, FLAGS.spec_freq, FLAGS.spec_time, mode=mode)\n        \n    if FLAGS.add_raw and (not FLAGS.lazy_spec):\n      eeg = self._gen_spec(row, idx, FLAGS.strategy, FLAGS.eeg_fn, FLAGS.spec_freq, FLAGS.spec_time, lazy=True, mode=mode)\n      eeg = np.clip(eeg, -FLAGS.oned_clip, FLAGS.oned_clip)\n      eeg /= FLAGS.oned_clip\n      if FLAGS.raw_center:\n        eeg_center = eeg[...,4000:6000]\n        if eeg_center.shape[-1] != X.shape[-1]:\n          eeg_center = resize(eeg_center, (eeg_center.shape[-2], X.shape[-1]))\n      if eeg.shape[-1] != X.shape[-1]:\n        eeg = resize(eeg, (eeg.shape[-2], X.shape[-1]))\n      \n      X = np.concatenate([eeg_center, eeg, X], axis=0)\n      \n    X2 = self._gen_spec(row, idx, FLAGS.strategy2, FLAGS.eeg_fn, FLAGS.spec2_freq, FLAGS.spec2_time, mode=mode)\n    if FLAGS.center_crop:\n      shift = 0\n      X2 = center_crop(X2, FLAGS.center_seconds / 50., \n                       shift=shift, sample_rate=200)\n    return [X, X2]\n  \n  def merge_specs(self, kaggle_spec, eeg_spec_list, mode='test'):\n    if kaggle_spec is None or not eeg_spec_list:\n      return BaseEEGBuilder.merge_specs(self, kaggle_spec, eeg_spec_list)\n    \n    if FLAGS.lazy_spec:\n      return {\n        'kaggle': kaggle_spec,\n        'eeg': eeg_spec_list[0],\n        'eeg2': eeg_spec_list[1],\n      }\n    \n    shape = (kaggle_spec.shape[0], FLAGS.spec_time - kaggle_spec.shape[1])\n\n    if eeg_spec_list[-1].shape != shape:\n      eeg_spec_list[-1] = resize(eeg_spec_list[-1], shape)\n\n    kaggle_spec = np.concatenate([kaggle_spec, eeg_spec_list[-1]], axis=1)\n    eeg_spec_list = eeg_spec_list[:-1]\n    return BaseEEGBuilder.merge_specs(self, kaggle_spec, eeg_spec_list)","metadata":{"execution":{"iopub.status.busy":"2024-04-12T12:58:28.259989Z","iopub.execute_input":"2024-04-12T12:58:28.260370Z","iopub.status.idle":"2024-04-12T12:58:28.304168Z","shell.execute_reply.started":"2024-04-12T12:58:28.260343Z","shell.execute_reply":"2024-04-12T12:58:28.303159Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"Dataset","metadata":{}},{"cell_type":"code","source":"class TorchDataset(Dataset):\n  \n  def __init__(self,\n               df,\n               mode='test'):\n    assert mode in ['train', 'eval', 'test', 'valid']\n    ic(mode, df)\n    self.df = df\n    self.mode = mode\n  \n    self.eeg_builder = EEGBuilder()\n    \n    # idx = self.rng.integers(len(self))\n    idx = 0\n    self.show(idx)\n    \n  def show(self, idx):\n    data = self[idx]\n    ic(self.mode, data)\n    if 'image' in data:\n      ic(data['image'].shape)\n    if 'eeg' in data:\n      ic(data['eeg'].shape)\n    \n  def __len__(self):\n    \"\"\"\n        Denotes the number of batches per epoch.\n        \"\"\"\n    return len(self.df)\n\n\n  def __getitem__(self, index):\n    \"\"\"\n        Generate one batch of data.\n    #     \"\"\"    \n    y = np.zeros(6, dtype='float32')\n    \n    start = None\n    \n    row = self.df.iloc[index]\n    row = row.copy()\n    idx = 0\n    r = 0\n\n    if FLAGS.use_specs:\n      #----kaggle specs----\n      X = None\n      kaggle_spec = None\n      if FLAGS.use_kaggle_specs:\n        X = np.zeros((400, 300), dtype='float32')\n        \n        def _get_kaggle_spec(spec_id):\n          mode_ = self.mode \n          spec = pd.read_parquet(f'{FLAGS.root}/{mode_}_spectrograms/{spec_id}.parquet')\n          spec = spec.to_numpy()[:, 1:]\n          return spec\n        spec = _get_kaggle_spec(row.spectrogram_id)    \n        img = spec[r:r + 300].T\n        \n        img = log_normalize(img, FLAGS.kaggle_spec_clip)\n        img = np.nan_to_num(img, nan=-1.)\n\n        X[:, :img.shape[1]] = img\n        kaggle_spec = X\n        ic(kaggle_spec.shape)\n        if FLAGS.kaggle_crop:\n          scale = FLAGS.kaggle_scale\n          shift = 0\n          X = center_crop(X, FLAGS.center_seconds / 600, scale=scale, shift=shift, sample_rate=0.5)\n          kaggle_spec = np.concatenate([kaggle_spec, X], axis=1)\n          ic(kaggle_spec.shape)\n      \n      #----------eeg spectrogram----------\n      eeg_spec_list = []\n      if FLAGS.use_eeg_specs:\n        mode = self.mode \n        eeg_spec_list = self.eeg_builder.get_eeg_specs(row, idx, mode=mode)\n\n    data = {}\n    if FLAGS.use_specs:\n      if not FLAGS.lazy_spec:\n        data['image'] = self.eeg_builder.merge_specs(kaggle_spec, eeg_spec_list)\n        data['image'] = data['image'].astype(np.float32)\n      else:\n        data.update(self.eeg_builder.merge_specs(kaggle_spec, eeg_spec_list))\n        for key in ['kaggle', 'eeg', 'eeg2']:\n          if key in data:\n            data[key] = data[key].astype(np.float32)      \n\n    if 'image' in data:\n      data['image'] = normalize_image(data['image'])\n      data['image'] = torch.from_numpy(data['image'])\n    return data","metadata":{"execution":{"iopub.status.busy":"2024-04-12T12:58:28.305739Z","iopub.execute_input":"2024-04-12T12:58:28.306136Z","iopub.status.idle":"2024-04-12T12:58:28.323837Z","shell.execute_reply.started":"2024-04-12T12:58:28.306109Z","shell.execute_reply":"2024-04-12T12:58:28.322893Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"test_ds = TorchDataset(test, 'test')\nic(len(test_ds))\n\nkwargs = {\n        'num_workers': FLAGS.num_workers,\n        'pin_memory': False,\n        'persistent_workers': True,\n        'collate_fn': None,\n    }\n\nsampler_test = None\ntest_dl = torch.utils.data.DataLoader(test_ds,\n                                    batch_size=FLAGS.batch_size,\n                                    sampler=sampler_test,\n                                    **kwargs)","metadata":{"execution":{"iopub.status.busy":"2024-04-12T12:58:28.324994Z","iopub.execute_input":"2024-04-12T12:58:28.325300Z","iopub.status.idle":"2024-04-12T12:58:29.936592Z","shell.execute_reply.started":"2024-04-12T12:58:28.325275Z","shell.execute_reply":"2024-04-12T12:58:29.935683Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"image = next(iter(test_dl))['image'][0].permute(1,2,0)\nimage = image.detach().cpu().numpy()\nic(image.shape, image.min(), image.max())\nplt.imshow(image[...,0], cmap='jet')","metadata":{"execution":{"iopub.status.busy":"2024-04-12T12:58:29.937731Z","iopub.execute_input":"2024-04-12T12:58:29.938019Z","iopub.status.idle":"2024-04-12T12:58:30.909897Z","shell.execute_reply.started":"2024-04-12T12:58:29.937995Z","shell.execute_reply":"2024-04-12T12:58:30.908848Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"Model","metadata":{}},{"cell_type":"code","source":"class BaseModel(nn.Module):\n  \n  def __init__(self):\n    super().__init__() \n    \n  def preprocess(self, inputs):\n    if 'image' in inputs:\n      x = inputs['image']\n    else:\n      # this is for torchaudio.stf...\n      pass\n            \n    return x\n\n  def work(self, x):\n    raise NotImplementedError\n  \n  def forward(self, inputs):\n    if FLAGS.work_mode == 'train':\n      self.input_ = inputs\n    x = self.preprocess(inputs)\n    if self.training:\n      res = self.work(x)\n    else:\n      res = self.work(x)\n      if FLAGS.tta:\n        x = torch.flip(x, dims=[-1])\n        res2 = self.work(x)\n        res['pred'] = (res['pred'] + res2['pred']) / 2\n    return res\n  \nclass Model(BaseModel):\n  \n  def __init__(self):\n    super().__init__()\n    pretrained = True\n    if FLAGS.mode == 'test':\n      pretrained = False\n    done = False\n    while not done:\n      try:\n        try:\n          self.backbone = timm.create_model(\n              FLAGS.backbone,\n              pretrained=pretrained,\n              drop_rate=FLAGS.drop_rate,\n              drop_path_rate=FLAGS.drop_path_rate,\n          )\n        except Exception as e:\n          # some models do not has drop_path_rate param like efficientvit\n          ic(e)\n          self.backbone = timm.create_model(\n              FLAGS.backbone,\n              pretrained=pretrained,\n              drop_rate=FLAGS.drop_rate,\n          )\n        done = True\n      except Exception as e:\n        ic(e)\n        pass\n\n#     data_config = timm.data.resolve_model_data_config(self.backbone)\n#     ic(data_config)\n\n    self.backbone.reset_classifier(0)\n    \n    ic(self.backbone.num_features)\n    \n    num_features = self.backbone.num_features\n    self.head = nn.Linear(num_features, len(TARGETS))\n    if FLAGS.mheads:\n      self.head2 = nn.Linear(num_features, len(TARGETS))\n\n    \n  def work(self, x):\n    if self.training:\n      inputs = self.input_\n    feat = self.backbone(x)\n        \n    x = self.head(feat)\n    if self.training and FLAGS.mheads:\n      x2 = self.head2(feat)\n      mask = (inputs['n_votes'] >= MIN_VOTES).float()\n      mask = mask.unsqueeze(-1)\n      x = x * mask + x2 * (1 - mask)\n    \n    res = {\n      'pred': x\n    }\n      \n    return res\n                  ","metadata":{"execution":{"iopub.status.busy":"2024-04-12T12:58:58.330532Z","iopub.execute_input":"2024-04-12T12:58:58.331375Z","iopub.status.idle":"2024-04-12T12:58:58.345775Z","shell.execute_reply.started":"2024-04-12T12:58:58.331346Z","shell.execute_reply":"2024-04-12T12:58:58.344764Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"Infer","metadata":{}},{"cell_type":"code","source":"def iterable(item):\n  \"\"\"\n  be careful!  like string 'abc' is iterable! \n  you may need to use if not isinstance(values, (list, tuple)):\n  \"\"\"\n  return isinstance(item, Iterable)\n\ndef squeeze(x, dim=-1):\n  if isinstance(x, (list, tuple)):\n    return x\n  if len(x.shape) > 1 and x.shape[dim] == 1:\n    return x.squeeze(dim)\n  return x\n\nclass Ensembler(object):\n\n  def __init__(self,\n               need_sort=False,\n               ignores=['id', 'ids', 'label', 'labels'],\n               includes=[],\n               id_key='id',\n               inplace=True):\n    self.x = None\n    self.need_sort = need_sort\n    self.weights = []\n    self.total_weight = 0\n    self.ignores = set(ignores)\n    self.includes = set(includes)\n    self.id_key = id_key\n    self.inplace = inplace\n\n  def is_ok(self, key):\n    if self.includes:\n      return key in self.includes\n    return key not in self.ignores\n\n  def add(self, x, weight=1., inplace=True):\n    if (not inplace) or (not self.inplace):\n      x = x.copy()\n    self.total_weight += weight\n    if self.need_sort:\n      inds = np.asarray(x[self.id_key]).argsort()\n      for key in x:\n        if (not self.is_ok(key)) and (key != self.id_key):\n          continue\n        try:\n          x[key] = x[key][inds]\n        except Exception:\n          # ic(key)\n          x[key] = [x[key][idx] for idx in inds]\n\n    if self.x is None:\n      # 注意这意味着第一个add的输入x 会被改变 如果需要连续使用 比如ensemble-select.py 传递.copy()\n      self.x = x\n      if iterable(weight) or weight != 1:\n        m = {k: [pred * weight for pred in x[k]] for k in x if self.is_ok(k)}\n        self.x.update(m)\n    else:\n      m = {\n          k: [pred1 + pred2 * weight for pred1, pred2 in zip(self.x[k], x[k])]\n          for k in x\n          if self.is_ok(k)\n      }\n      self.x.update(m)\n\n  def finalize(self):\n    x = self.x\n    weight = self.total_weight\n    for k in x:\n      if not self.is_ok(k):\n        continue\n      x[k] = [pred / weight for pred in x[k]]\n    return x\n\n  def adds(self, xs, weights=None, inplace=False):\n    if not inplace:\n      xs = [x.copy() for x in xs]\n    if weights is not None:\n      for x, weight in zip(xs, weights):\n        self.add(x, weight)\n    else:\n      for x in xs:\n        self.add(x)\n    return self.finalize()\n\ndef get_device():\n  return torch.device(\"cuda\" if torch.cuda.is_available() else \"cpu\")\n\ndef load_weights(model,\n                 path,\n                 map_location=None,\n                 return_checkpoint=False,\n                 return_updated=False,\n                 renames={},\n                 includes=None,\n                 excludes=None,\n                 to_device=True,\n                 eval=True,\n                 strict=False):\n  checkpoint = torch.load(path, map_location=map_location)\n  state = checkpoint['state_dict']\n  # https://zhuanlan.zhihu.com/p/601044938\n  # 这段代码是在修复checkpoint中的state_dict的key。\n  # 在某些情况下，state_dict的key会带有一个\"_orig_mod.\"的前缀，\n  # 这段代码就是在遍历state_dict的所有键值对，如果键值对的键以\"_orig_mod.\"开头，\n  # 那么就将这个键值对的键去掉\"_orig_mod.\"前缀，并将这个键值对从state_dict中移除。\n  unwanted_prefix = '_orig_mod.'\n  for k,v in list(state.items()):\n    if k.startswith(unwanted_prefix):\n      state[k[len(unwanted_prefix):]] = state.pop(k)\n      \n  # ic(gezi.get_mem_gb())\n  model_ = model.module if hasattr(model, 'module') else model\n  \n  if strict:\n    model_.load_state_dict(state, strict=True)\n  else:\n    full_update = True\n    model_state_dict = model_.state_dict()\n\n    def is_ok(key):\n      if includes:\n        for incl_key in includes:\n          if incl_key in key:\n            return True\n        return False\n      if excludes:\n        for excl_key in excludes:\n          if excl_key in key:\n            return False\n      return True\n\n    mismatch_ignores = set()\n    for key in model_state_dict:\n      if key not in state or state[key].shape != model_state_dict[key].shape:\n        mismatch_ignores.add(key)\n        full_update = False\n    if mismatch_ignores:\n      ic(mismatch_ignores)\n    additional_ignores = set()\n    for key in state:\n      if key not in model_state_dict:\n        additional_ignores.add(key)\n        full_update = False\n    if additional_ignores:\n      ic(additional_ignores)\n    if full_update:\n      model_.load_state_dict(state)\n    else:\n      new_params = model_state_dict\n      # ic(new_params.keys())\n      if not renames:\n        new_params.update({\n            k: v\n            for k, v in state.items()\n            if (k in new_params) and (k not in mismatch_ignores) and is_ok(k)\n        })\n      else:\n        ori = list(renames.keys())[0]\n        dest = list(renames.values())[0]\n        new_params.update({\n            k.replace(ori, dest): v\n            for k, v in state.items()\n            if k.replace(ori, dest) in new_params and\n            k.replace(ori, dest) not in mismatch_ignores\n        })\n\n      model_.load_state_dict(new_params)\n\n  del checkpoint['state_dict']\n  if not return_checkpoint:\n    del state\n    del checkpoint\n    import gc\n    gc.collect()\n\n  if to_device:\n    device = get_device()\n    model.to(device)\n  if eval:\n    model.eval()\n\n  if not return_checkpoint:\n    return\n\n  if not return_updated:\n    return checkpoint\n\n  updated_params = []\n  for name, param in model_.named_parameters():\n    if name in state:\n      updated_params.append(param)\n\n  return checkpoint, updated_params\n\ndef infer(models,\n          inputs,\n          weights=None,\n          post_fn=None,\n          batch_size=None,\n          desc='Infering',\n          dynamic_keys=[],\n          out_keys=[],\n          mask_key=None,\n          out_hook=None,\n          pred_key='pred',\n          amp=False,\n          fp16=False,\n          bfloat16=False,\n          verbose=0):\n  if not weights:\n    weights = [1.] * len(models)\n  for i in range(len(models)):\n    models[i] = nn.DataParallel(models[i])\n    device = torch.device(\"cuda\" if torch.cuda.is_available() else \"cpu\")\n    models[i].to(device)\n    \n    if fp16:\n      if not bfloat16:\n        models[i].half()\n      else:\n        models[i] = modeles[i].to(dtype=torch.bfloat16)\n    else:\n      amp = False\n    \n  with torch.no_grad():\n    input_is_dict = True\n    if isinstance(inputs, (list, tuple)):\n      if len(inputs) == 2:\n        inputs = inputs[0]\n\n    if isinstance(inputs, dict):\n      if 0 in inputs:\n        return predicts(model, inputs, batch_size, desc, dynamic_keys, mask_key)\n      inputs_ = {}\n      for key in inputs:\n        if (not type(inputs[key][0]) in [np.str_, str]):\n          inputs_[key] = inputs[key]\n      inputs = inputs_\n      # TODO support dynamic length with data collactor padding to max lenght in a batch\n      dataset = Dataset.from_dict(inputs)\n      dataset.set_format(type='torch', device=device)\n      assert batch_size, 'need batch size if your inputs is not dataloader but dict'\n      dataloader = torch.utils.data.DataLoader(dataset, batch_size=batch_size)\n    elif isinstance(inputs, pd.DataFrame):\n      input_is_dict = True\n      dataset = Dataset.from_pandas(inputs)\n      dataset.set_format(type='torch', device=device)\n      assert batch_size, 'need batch size if your inputs is not dataloader but dict'\n      dataloader = torch.utils.data.DataLoader(dataset, batch_size=batch_size)\n    else:\n      input_is_dict = False\n      dataloader = inputs\n\n    res = None\n    first_batch = True\n    for inputs in tqdm(dataloader, desc=desc):\n      if isinstance(inputs, (list, tuple)):\n        if len(inputs) == 2:\n          inputs = inputs[0]\n      if mask_key is not None:\n        max_len = inputs[mask_key].sum(1).max()\n        for key in dynamic_keys + [mask_key]:\n          if key in inputs:\n            inputs[key] = inputs[key][:, :max_len]\n      if not input_is_dict:\n        for key in inputs:\n          if isinstance(inputs[key], torch.Tensor):\n            inputs[key] = inputs[key].to(device)\n    \n      ensembler = Ensembler()\n      for i, model in enumerate(models):\n        if not amp:\n          preds = model(inputs)\n        else:\n          dtype = torch.float16 if not bfloat16 else torch.bfloat16\n          with torch.cuda.amp.autocast(dtype=dtype):\n            preds = model(inputs)\n          \n        if isinstance(preds, dict):\n          for key in preds:\n            preds[key] = squeeze(preds[key].detach().cpu().numpy())\n          \n          inputs_ = {k: inputs[k] for k in inputs if k in out_keys}\n          for key in inputs_:\n            if torch.is_tensor(inputs_[key]):\n              inputs_[key] = inputs_[key].detach().cpu().numpy()\n          \n          if first_batch and verbose > 0:\n            ic(i, preds[pred_key][0])\n          if out_hook is not None:\n            out_hook(preds, inputs_)\n          if post_fn is not None:\n            preds = post_fn(preds)\n            if first_batch and verbose > 0:\n              ic(i, preds[pred_key][0])\n        else:\n          preds = squeeze(preds.detach().cpu().numpy())\n          if first_batch and verbose > 0:\n            ic(i, preds[0])\n          if post_fn is not None:\n            preds = post_fn(preds)\n            if first_batch and verbose > 0:\n              ic(i, preds[0])\n\n        ensembler.add(preds, weights[i])\n      preds = ensembler.finalize()\n      first_batch = False\n      \n      if isinstance(preds, dict):\n        if not res:\n          res = {key: [] for key in preds}\n          for key in inputs_:\n            res[key] = []\n        for key in preds:\n          res[key].append(preds[key])\n        for key in inputs_:\n          res[key].append(inputs_[key])\n      else:\n        if not res:\n          res = []\n        res.append(preds)\n\n    if isinstance(res, dict):\n      for key in res:\n        try:\n          res[key] = np.concatenate(res[key])\n        except Exception as e:\n          # ic(key, e)\n          l = []\n          for l_ in res[key]:\n            l.extend(l_)\n          res[key] = l\n    else:\n      try:\n        res = np.contanate(res[key])\n      except Exception:\n        l = []\n        for l_ in res:\n          l.extend(l_)\n        res = l\n    return res\n  \ndef torch_softmax(x, axis=-1):\n  return nn.Softmax(dim=axis)(torch.tensor(x)).numpy()","metadata":{"_kg_hide-input":true,"_kg_hide-output":true,"execution":{"iopub.status.busy":"2024-04-12T12:58:58.653251Z","iopub.execute_input":"2024-04-12T12:58:58.653614Z","iopub.status.idle":"2024-04-12T12:58:58.707578Z","shell.execute_reply.started":"2024-04-12T12:58:58.653584Z","shell.execute_reply":"2024-04-12T12:58:58.706550Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"root = FLAGS.model_dir\nnum_models = int(open(f'{root}/num_models.txt').readline().strip())\nic(num_models)\nmodels = []\nfor fold in tqdm(range(num_models), desc='folds'):\n  model_dir = f'{root}/{fold}'\n  model = Model()\n  try:\n    display(pd.read_csv(f'{model_dir}/metrics.csv'))\n    ic(open(f'{root}/path.txt').readline())\n  except Exception as e:\n    ic(e)\n  load_weights(model, f'{model_dir}/model.pt', strict=False)\n  models.append(model)\n\ndef post_deal(x):\n  x['pred'] = list(torch_softmax(np.array(list(x['pred'])).astype(np.float32)))\n  return x\nx = infer(models, test_dl, post_fn=post_deal, amp=False, fp16=False, verbose=1)","metadata":{"execution":{"iopub.status.busy":"2024-04-12T12:58:58.806140Z","iopub.execute_input":"2024-04-12T12:58:58.806977Z","iopub.status.idle":"2024-04-12T12:59:05.670435Z","shell.execute_reply.started":"2024-04-12T12:58:58.806941Z","shell.execute_reply":"2024-04-12T12:59:05.669449Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"TARGETS = ['seizure_vote', 'lpd_vote', 'gpd_vote', 'lrda_vote', 'grda_vote', 'other_vote']\nsub = pd.DataFrame({'eeg_id': test.eeg_id.values})\nsub[TARGETS] = x['pred'][:len(test)]\nsub.to_csv('submission.csv',index=False)\nprint(f'Submissionn shape: {sub.shape}')\nsub.head()","metadata":{"execution":{"iopub.status.busy":"2024-04-12T12:59:05.672062Z","iopub.execute_input":"2024-04-12T12:59:05.672373Z","iopub.status.idle":"2024-04-12T12:59:05.691344Z","shell.execute_reply.started":"2024-04-12T12:59:05.672347Z","shell.execute_reply":"2024-04-12T12:59:05.690666Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"","metadata":{},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"","metadata":{},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"","metadata":{},"execution_count":null,"outputs":[]}]}