{"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":"gpu","dataSources":[{"sourceId":71549,"databundleVersionId":8561470,"sourceType":"competition"},{"sourceId":9195731,"sourceType":"datasetVersion","datasetId":5559249},{"sourceId":193161758,"sourceType":"kernelVersion"},{"sourceId":194526416,"sourceType":"kernelVersion"}],"dockerImageVersionId":30747,"isInternetEnabled":true,"language":"python","sourceType":"notebook","isGpuEnabled":true}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"markdown","source":"# YOLO Explanation\n\n\nI have never worked with YOLO before. So I tried to underastand what the orignal author Liam Nguyen did in his [notbook](https://www.kaggle.com/code/namgalielei/lsdc-train-yolo-ss). This is just his code with explanation.\n\n\nBroad Workflow: \n\nWe repeat the steps for each of the ailment. Currently we only work with SS in this notebook\n- Step - 1: Data processing: The XY coordinates for every series is brought together in filtered_df\n- Step - 2: Data creation: For evey unique combination of 'study_id', 'series_id', 'instance_number', we create a image file (.jpg) and label file(.txt). The label file has bounding box description as x,y,height,width for every point of interest in the instance. Data is created here: https://www.kaggle.com/code/namgalielei/lsdc-gen-yolo-data-ss. Remember, currently accepted directory for training is:\n\n\n    --- Root Dir\n        --- images\n            --- train\n            --- val\n            --- test\n        --- labels\n            --- train\n            --- val\n            --- test\n            \n            \n- Step - 3: Train YOLO. Mentioned in this notebook.","metadata":{}},{"cell_type":"markdown","source":"# Imports ","metadata":{}},{"cell_type":"code","source":"import os\nimport pandas as pd\nimport numpy as np\nimport pydicom\nfrom tqdm.auto import tqdm\nimport matplotlib.pyplot as plt\nimport cv2\nimport glob\n","metadata":{"execution":{"iopub.status.busy":"2024-09-10T10:38:35.522034Z","iopub.execute_input":"2024-09-10T10:38:35.522703Z","iopub.status.idle":"2024-09-10T10:38:36.422392Z","shell.execute_reply.started":"2024-09-10T10:38:35.522669Z","shell.execute_reply":"2024-09-10T10:38:36.421401Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# Install and Data Creation ","metadata":{}},{"cell_type":"code","source":"# Install the ultralytics package from GitHub\n!pip install git+https://github.com/ultralytics/ultralytics.git@main\n!unzip -q /kaggle/input/lsdc-gen-yolo-data-ss/data_fold0.zip","metadata":{"execution":{"iopub.status.busy":"2024-09-10T10:38:36.424240Z","iopub.execute_input":"2024-09-10T10:38:36.424759Z","iopub.status.idle":"2024-09-10T10:39:24.102635Z","shell.execute_reply.started":"2024-09-10T10:38:36.424727Z","shell.execute_reply":"2024-09-10T10:39:24.101378Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"from ultralytics import YOLO\nimport wandb\nfrom wandb.integration.ultralytics import add_wandb_callback\nfrom kaggle_secrets import UserSecretsClient","metadata":{"execution":{"iopub.status.busy":"2024-09-10T10:39:24.104223Z","iopub.execute_input":"2024-09-10T10:39:24.104576Z","iopub.status.idle":"2024-09-10T10:39:28.838584Z","shell.execute_reply.started":"2024-09-10T10:39:24.104528Z","shell.execute_reply":"2024-09-10T10:39:28.837805Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"ls","metadata":{"execution":{"iopub.status.busy":"2024-09-10T10:39:28.840981Z","iopub.execute_input":"2024-09-10T10:39:28.841776Z","iopub.status.idle":"2024-09-10T10:39:29.940300Z","shell.execute_reply.started":"2024-09-10T10:39:28.841749Z","shell.execute_reply":"2024-09-10T10:39:29.939083Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"IMG_DIR = \"/kaggle/input/rsna-2024-lumbar-spine-degenerative-classification/train_images\"\n\nFOLD = 0\n\n# Image Size \nOD_INPUT_SIZE = 384\n# Box Size\nSTD_BOX_SIZE = 20\n\nBATCH_SIZE = 64\nEPOCHS = 50\nSAMPLE = None\n\n# 5 levels * 2 positions (left/right) * 3 conditions = 30 labels\nCONDITIONS = ['Left Subarticular Stenosis', 'Right Subarticular Stenosis']\nSEVERITIES = ['Normal/Mild', 'Moderate', 'Severe']\nLEVELS = ['l1_l2', 'l2_l3', 'l3_l4', 'l4_l5', 'l5_s1']\nDATA_DIR = f'data_fold{FOLD}'\n\n# Read train data \ntrain_val_df = pd.read_csv('/kaggle/input/rsna-2024-lumbar-spine-degenerative-classification/train.csv')\ntrain_xy = pd.read_csv('/kaggle/input/rsna-2024-lumbar-spine-degenerative-classification/train_label_coordinates.csv')\ntrain_des = pd.read_csv('/kaggle/input/rsna-2024-lumbar-spine-degenerative-classification/train_series_descriptions.csv')\n\nif SAMPLE:\n    train_val_df = train_val_df.sample(SAMPLE, random_state=2698)\n    \n# Presplit for folds\nfold_df = pd.read_csv('/kaggle/input/lsdc-fold-split/5folds.csv')\n\nprint('Unique conditions  >   ', train_xy['condition'].unique())\n\ntrain_xy.head(3)","metadata":{"execution":{"iopub.status.busy":"2024-09-10T10:39:29.941955Z","iopub.execute_input":"2024-09-10T10:39:29.942275Z","iopub.status.idle":"2024-09-10T10:39:30.158258Z","shell.execute_reply.started":"2024-09-10T10:39:29.942248Z","shell.execute_reply":"2024-09-10T10:39:30.157304Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# Helper Functions","metadata":{}},{"cell_type":"code","source":"def get_level(text):\n    \"\"\"\n    This function takes a string input 'text' and searches for level indicators\n    (such as 'l1_l2', 'l2_l3', 'l3_l4', 'l4_l5', 'l5_s1') within the text.\n    \n    If no level is found in the text, the function raises a ValueError.\n    \n    Example:\n    - Input: \"The project level is l3_l4.\"\n    - Output: \"L3/L4\"\n    \"\"\"\n    for lev in ['l1_l2', 'l2_l3', 'l3_l4', 'l4_l5', 'l5_s1']:\n        if lev in text:\n            split = lev.split('_')\n            split[0] = split[0].capitalize()\n            split[1] = split[1].capitalize()\n            return '/'.join(split)\n    raise ValueError('Level not found: ' + lev)\n    \n    \n    \ndef get_condition(text):\n    \"\"\"\n    This function takes a string input 'text' where words are separated by underscores ('_'). \n    It splits the text into individual words, capitalizes each word, and removes the last two words. \n    The remaining words are then joined together into a single string, separated by spaces.\n\n    Parameters:\n    - text (str): The input string with words separated by underscores.\n\n    Returns:\n    - str: A string of capitalized words, with the last two words removed, and joined by spaces.\n\n    Example:\n    - Input: \"example_text_to_modify\"\n    - Output: \"Example Text\"\n    \"\"\"\n    split = text.split('_')\n    for i in range(len(split)):\n        split[i] = split[i].capitalize()\n    split = split[:-2]\n    return ' '.join(split)\n\ndef query_train_xy_row(study_id, series_id=None, instance_num=None):\n    \"\"\"\n    This function queries a DataFrame `label_df` based on the provided study ID, \n    series ID, and instance number to retrieve the relevant rows.\n\n    Depending on the combination of the `study_id`, `series_id`, and `instance_num` parameters, \n    it filters the DataFrame as follows:\n    \n    - If both `series_id` and `instance_num` are provided, the function returns rows \n      that match all three criteria (study_id, series_id, instance_num).\n    - If neither `series_id` nor `instance_num` are provided, it returns rows that match only the `study_id`.\n    - If only `series_id` is provided, it returns rows that match the `study_id` and `series_id`.\n\n    Parameters:\n    - study_id (int/str): The ID of the study to filter by.\n    - series_id (int/str, optional): The ID of the series to filter by (default is None).\n    - instance_num (int, optional): The instance number to filter by (default is None).\n\n    Returns:\n    - DataFrame: A filtered subset of `label_df` based on the provided criteria.\n\n    Example:\n    - Input: query_train_xy_row(1001, 2002, 3)\n    - Output: Returns rows from `label_df` where `study_id` = 1001, `series_id` = 2002, and `instance_number` = 3.\n    \"\"\"\n    if series_id is not None and instance_num is not None:\n        return label_df[(label_df.study_id==study_id) & (label_df.series_id==series_id) &\n            (label_df.instance_number==instance_num)]\n    elif series_id is None and instance_num is None:\n        return label_df[(label_df.study_id==study_id)]\n    else:\n        return label_df[(train_xy.study_id==study_id) & (label_df.series_id==series_id)]\n    \n    \n    \ndef read_dcm(src_path):\n    # Reads DCMs\n    dicom_data = pydicom.dcmread(src_path)\n    image = dicom_data.pixel_array\n    image = (image - image.min()) / (image.max() - image.min() +1e-6) * 255\n    image = np.stack([image]*3, axis=-1).astype('uint8')\n    return image\n\n\ndef get_accronym(text):\n    # Gets Acronyms\n    split = text.split(' ')\n    return ''.join([x[0] for x in split])","metadata":{"execution":{"iopub.status.busy":"2024-09-10T10:39:30.159416Z","iopub.execute_input":"2024-09-10T10:39:30.159718Z","iopub.status.idle":"2024-09-10T10:39:30.176823Z","shell.execute_reply.started":"2024-09-10T10:39:30.159688Z","shell.execute_reply":"2024-09-10T10:39:30.175780Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# Data Creation","metadata":{}},{"cell_type":"code","source":"# For every study_id, we get corresponding condition level and label and assign a fold \nlabel_df = {'study_id':[], 'condition': [], 'level':[], 'label':[]}\n\nfor i, row in train_val_df.iterrows():\n    study_id = row['study_id']\n    for k, label in row.iloc[1:].to_dict().items():\n        level = get_level(k)\n        condition = get_condition(k)\n        label_df['study_id'].append(study_id)\n        label_df['condition'].append(condition)\n        label_df['level'].append(level)\n        label_df['label'].append(label)\nlabel_df = pd.DataFrame(label_df)\n# Assigning Fold\nlabel_df = label_df.merge(fold_df, on='study_id')\ntrain_xy = train_xy.merge(train_des, how='inner', on=['study_id', 'series_id'])\nlabel_df = label_df.merge(train_xy, how='inner', on=['study_id', 'condition', 'level'])\ndel train_xy\n# Create df for current disease conditions only\nfiltered_df = label_df[label_df.condition.map(lambda x: x in CONDITIONS)]\n\n#Dicts for mapping label <-> ID both ways \nlabel2id = {}\nid2label = {}\ni = 0\nfor cond in CONDITIONS:\n    for level in LEVELS:\n        for severity in SEVERITIES:\n            cls_ = f\"{cond.lower().replace(' ', '_')}_{level}_{severity.lower()}\"\n            label2id[cls_] = i\n            id2label[i] = cls_\n            i+=1\n\n# Train val creation \ntrain_df = filtered_df[filtered_df.fold != FOLD]\nval_df = filtered_df[filtered_df.fold == FOLD]\n\nfiltered_df.head()","metadata":{"execution":{"iopub.status.busy":"2024-09-10T10:39:30.178042Z","iopub.execute_input":"2024-09-10T10:39:30.178495Z","iopub.status.idle":"2024-09-10T10:39:30.945930Z","shell.execute_reply.started":"2024-09-10T10:39:30.178471Z","shell.execute_reply":"2024-09-10T10:39:30.944967Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# Test bounding box on sample image","metadata":{"execution":{"iopub.status.busy":"2024-08-20T15:38:20.437482Z","iopub.execute_input":"2024-08-20T15:38:20.437758Z","iopub.status.idle":"2024-08-20T15:38:20.44681Z","shell.execute_reply.started":"2024-08-20T15:38:20.437736Z","shell.execute_reply":"2024-08-20T15:38:20.446021Z"}}},{"cell_type":"code","source":"ex = label_df.sample(1).iloc[0]\nstudy_id = ex.study_id\nseries_id = ex.series_id\ninstance_num = ex.instance_number\n\nWIDTH = 10\n\npath = os.path.join(IMG_DIR, str(study_id), str(series_id), f'{instance_num}.dcm')\n\nimg = read_dcm(path)\n\ntmp_df = query_train_xy_row(study_id, series_id, instance_num)\nfor i, row in tmp_df.iterrows():\n    # condition_level\n    lbl = f\"{get_accronym(row['condition'])}_{row['level']}\"\n    x, y = row['x'], row['y']\n    x1 = int(x - WIDTH)\n    x2 = int(x + WIDTH)\n    y1 = int(y - WIDTH)\n    y2 = int(y + WIDTH)\n    color = None\n    if row['label'] == 'Normal/Mild':\n        color =  (0, 255, 0)\n    elif row['label'] == 'Moderate':\n        color = (255,255,0) \n    elif row['label'] == 'Severe':\n        color = (255,0,0)\n        \n    fontFace = cv2.FONT_HERSHEY_SIMPLEX\n    fontScale = 0.5\n    thickness = 1\n    cv2.rectangle(img, (x1,y1), (x2,y2), color, 2)\n    cv2.putText(img, lbl, (x1,y1), fontFace, fontScale, color, thickness, cv2.LINE_AA)\n\nprint(tmp_df)\n\nplt.imshow(img)\nplt.show()","metadata":{"execution":{"iopub.status.busy":"2024-09-10T10:39:30.947091Z","iopub.execute_input":"2024-09-10T10:39:30.947359Z","iopub.status.idle":"2024-09-10T10:39:31.397398Z","shell.execute_reply.started":"2024-09-10T10:39:30.947337Z","shell.execute_reply":"2024-09-10T10:39:31.396390Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# # test generated annotations\n\n_IM_DIR = f'{DATA_DIR}/images/train'\n_ANN_DIR = f'{DATA_DIR}/labels/train'\nname = np.random.choice(os.listdir(_IM_DIR))[:-4]\nprint(name)\n\n\nim = plt.imread(os.path.join(_IM_DIR, name+'.jpg')).copy()\nH,W = im.shape[:2]\nanns = np.loadtxt(os.path.join(_ANN_DIR, name+'.txt')).reshape(-1, 5)\n\nfor _cls, x,y,w,h in anns.tolist():\n    x *= W\n    y *= H\n    w *= W\n    h *= H\n    x1 = int(x-w/2)\n    x2 = int(x+w/2)\n    y1 = int(y-h/2)\n    y2 = int(y+h/2)\n    label = id2label[_cls]\n    \n    if _cls == 0:\n        c = (255,0,0)\n    elif _cls == 1:\n        c = (0,255,0)\n    else:\n        c = (255,255,0)\n\n    im = cv2.rectangle(im, (x1,y1), (x2,y2), c, 2)\n    cv2.putText(im, label, (x1,y1), fontFace, 0.3, c, 1, cv2.LINE_AA)\nplt.imshow(im)","metadata":{"execution":{"iopub.status.busy":"2024-09-10T10:40:07.088399Z","iopub.execute_input":"2024-09-10T10:40:07.088827Z","iopub.status.idle":"2024-09-10T10:40:07.511595Z","shell.execute_reply.started":"2024-09-10T10:40:07.088797Z","shell.execute_reply":"2024-09-10T10:40:07.510580Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# ls data_fold0/labels/train\n\nfile_path = \"data_fold0/labels/train/3106471623_2210516913_20.txt\"\n\n# Open and read the text file\nwith open(file_path, 'r') as file:\n    content = file.read()\n\n# Display the contents of the file\nprint(content)","metadata":{"execution":{"iopub.status.busy":"2024-09-10T10:40:20.061160Z","iopub.execute_input":"2024-09-10T10:40:20.061524Z","iopub.status.idle":"2024-09-10T10:40:20.067929Z","shell.execute_reply.started":"2024-09-10T10:40:20.061493Z","shell.execute_reply":"2024-09-10T10:40:20.066815Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"from PIL import Image\nimport matplotlib.pyplot as plt\nimage = Image.open('data_fold0/images/train/3106471623_2210516913_20.jpg')\nplt.imshow(image)\nplt.axis('off')  # Turn off axis labels\nplt.show()","metadata":{"execution":{"iopub.status.busy":"2024-09-10T10:40:31.644345Z","iopub.execute_input":"2024-09-10T10:40:31.645406Z","iopub.status.idle":"2024-09-10T10:40:31.844177Z","shell.execute_reply.started":"2024-09-10T10:40:31.645366Z","shell.execute_reply":"2024-09-10T10:40:31.843096Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# Define Config for YOLO","metadata":{}},{"cell_type":"code","source":"%%writefile yolo_scs.yaml\npath: /kaggle/working/data_fold0 # dataset root dir\ntrain: images/train  \nval: images/val \ntest: images/val \n\n# Classes\nnames:\n    0: left_subarticular_stenosis_l1_l2_normal/mild\n    1: left_subarticular_stenosis_l1_l2_moderate\n    2: left_subarticular_stenosis_l1_l2_severe\n    3: left_subarticular_stenosis_l2_l3_normal/mild\n    4: left_subarticular_stenosis_l2_l3_moderate\n    5: left_subarticular_stenosis_l2_l3_severe\n    6: left_subarticular_stenosis_l3_l4_normal/mild\n    7: left_subarticular_stenosis_l3_l4_moderate\n    8: left_subarticular_stenosis_l3_l4_severe\n    9: left_subarticular_stenosis_l4_l5_normal/mild\n    10: left_subarticular_stenosis_l4_l5_moderate\n    11: left_subarticular_stenosis_l4_l5_severe\n    12: left_subarticular_stenosis_l5_s1_normal/mild\n    13: left_subarticular_stenosis_l5_s1_moderate\n    14: left_subarticular_stenosis_l5_s1_severe\n    15: right_subarticular_stenosis_l1_l2_normal/mild\n    16: right_subarticular_stenosis_l1_l2_moderate\n    17: right_subarticular_stenosis_l1_l2_severe\n    18: right_subarticular_stenosis_l2_l3_normal/mild\n    19: right_subarticular_stenosis_l2_l3_moderate\n    20: right_subarticular_stenosis_l2_l3_severe\n    21: right_subarticular_stenosis_l3_l4_normal/mild\n    22: right_subarticular_stenosis_l3_l4_moderate\n    23: right_subarticular_stenosis_l3_l4_severe\n    24: right_subarticular_stenosis_l4_l5_normal/mild\n    25: right_subarticular_stenosis_l4_l5_moderate\n    26: right_subarticular_stenosis_l4_l5_severe\n    27: right_subarticular_stenosis_l5_s1_normal/mild\n    28: right_subarticular_stenosis_l5_s1_moderate\n    29: right_subarticular_stenosis_l5_s1_severe","metadata":{"execution":{"iopub.status.busy":"2024-09-10T09:57:24.941456Z","iopub.execute_input":"2024-09-10T09:57:24.941834Z","iopub.status.idle":"2024-09-10T09:57:24.949713Z","shell.execute_reply.started":"2024-09-10T09:57:24.941805Z","shell.execute_reply":"2024-09-10T09:57:24.948771Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# WANDB Login ","metadata":{}},{"cell_type":"code","source":"user_secrets = UserSecretsClient()\nsecret_value_0 = user_secrets.get_secret(\"WANDB\")\nwandb.login(key=secret_value_0)\n# Initialize W&B run\nwandb.init(\n    project=\"lsdc_yolov8\",\n    group=\";\".join(CONDITIONS),\n)","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# Train Model","metadata":{}},{"cell_type":"code","source":"# Initialize YOLO Model\nmodel = YOLO(\"yolov8s.pt\")\n\n# Add W&B callback for Ultralytics\nadd_wandb_callback(model, enable_model_checkpointing=True)\n\nmodel.train(project=\"lsdc_yolov8\", data=\"yolo_scs.yaml\", \n            epochs=EPOCHS, imgsz=OD_INPUT_SIZE, batch=BATCH_SIZE)\n\n# Finish the W&B run\nwandb.finish()","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"","metadata":{}},{"cell_type":"code","source":"","metadata":{},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# # test generated annotations\n\n_IM_DIR = f'{DATA_DIR}/images/val'\n_ANN_DIR = f'{DATA_DIR}/labels/val'\nname = np.random.choice(os.listdir(_IM_DIR))[:-4]\n\npath = os.path.join(_IM_DIR, name+'.jpg')\n\nim = plt.imread(path).copy()\nH,W = im.shape[:2]\nanns = np.loadtxt(os.path.join(_ANN_DIR, name+'.txt')).reshape(-1, 5)\n\nfor _cls, x,y,w,h in anns.tolist():\n    x *= W\n    y *= H\n    w *= W\n    h *= H\n    x1 = int(x-w/2)\n    x2 = int(x+w/2)\n    y1 = int(y-h/2)\n    y2 = int(y+h/2)\n    label = id2label[_cls]\n    print(label)\n    \n    if _cls == 0:\n        c = (255,0,0)\n    elif _cls == 1:\n        c = (0,255,0)\n    else:\n        c = (255,255,0)\n#     c = (0,255,255)\n\n    im = cv2.rectangle(im, (x1,y1), (x2,y2), c, 2)\n    cv2.putText(im, label, (x1,y1), fontFace, 0.3, c, 1, cv2.LINE_AA)\n\n\nplt.imshow(im)","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# Initialize YOLO Model\nmodel = YOLO(glob.glob(\"lsdc_yolov8/*/weights/best.pt\")[0])\n\n# Add W&B callback for Ultralytics\n# add_wandb_callback(model, enable_model_checkpointing=True)\n\n# Perform prediction which automatically logs to a W&B Table\n# with interactive overlays for bounding boxes, segmentation masks\nout = model.predict([path], save=True, conf=0.2)\nim = plt.imread(glob.glob(f'{out[0].save_dir}/*.jpg')[0])\nplt.imshow(im)","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"","metadata":{"trusted":true},"execution_count":null,"outputs":[]}]}