{"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":71549,"databundleVersionId":8561470,"sourceType":"competition"}],"dockerImageVersionId":30698,"isInternetEnabled":true,"language":"python","sourceType":"notebook","isGpuEnabled":false}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"markdown","source":"## Create Patches\n\n- save the patches of image pathology as a csv for smaller storage\n- each pixel is one column\n- the pixel data is concatenated with the train_label_coordinates data and train data\n- train data is transformed to match train_label_coordinates columns for merging\n- reshape to reconstruct patch as image\n- output file 'patches_50px.csv'","metadata":{"execution":{"iopub.status.busy":"2024-05-29T14:09:13.355438Z","iopub.execute_input":"2024-05-29T14:09:13.355829Z","iopub.status.idle":"2024-05-29T14:09:13.360586Z","shell.execute_reply.started":"2024-05-29T14:09:13.355798Z","shell.execute_reply":"2024-05-29T14:09:13.359486Z"}}},{"cell_type":"code","source":"!pip install dicomsdl -q","metadata":{"execution":{"iopub.status.busy":"2024-05-30T17:04:21.833539Z","iopub.execute_input":"2024-05-30T17:04:21.833974Z","iopub.status.idle":"2024-05-30T17:04:38.957496Z","shell.execute_reply.started":"2024-05-30T17:04:21.833942Z","shell.execute_reply":"2024-05-30T17:04:38.956383Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"from pathlib import Path\nfrom matplotlib import pyplot as plt\n\nroot = Path('/kaggle/input/rsna-2024-lumbar-spine-degenerative-classification/train_images')","metadata":{"execution":{"iopub.status.busy":"2024-05-30T17:04:38.959375Z","iopub.execute_input":"2024-05-30T17:04:38.959715Z","iopub.status.idle":"2024-05-30T17:04:38.966206Z","shell.execute_reply.started":"2024-05-30T17:04:38.959684Z","shell.execute_reply":"2024-05-30T17:04:38.965092Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"#https://www.perplexity.ai/ with edits\n\nimport numpy as np\nimport dicomsdl\n\ndef mri_dicom_to_array(dicom_file):\n    \"\"\"\n    Convert a DICOM image to a PNG format with specified window center and width.\n\n    Args:\n        dicom_file (str): Path to the DICOM file.\n        window_center (int or float): Window center value.\n        window_width (int or float): Window width value.\n\n    Returns:\n        PIL Image\n    \"\"\"\n    ## Load the DICOM file\n    ds = dicomsdl.open(dicom_file)\n\n    ## Extract the pixel data from the DICOM file\n    #pixel_data = ds.pixel_array\n    pixel_data = ds.pixelData()\n    \n    # Apply RescaleIntercept and RescaleSlope if present\n    rescale_intercept = ds.RescaleIntercept if 'RescaleIntercept' in ds else 0\n    rescale_slope = ds.RescaleSlope if 'RescaleSlope' in ds else 1\n    pixel_data = pixel_data * rescale_slope + rescale_intercept\n    \n    # Handle different PhotometricInterpretations\n    photometric_interpretation = ds.PhotometricInterpretation\n    if photometric_interpretation == \"MONOCHROME1\":\n        pixel_data = np.invert(pixel_data)\n    elif photometric_interpretation == \"MONOCHROME2\":\n        pass  # No need to invert\n    else:\n        raise ValueError(f\"Unsupported PhotometricInterpretation: {photometric_interpretation}\")\n\n    ## Get window center and width from DICOM\n    window_center, window_width = get_window_values(ds)\n    \n    ## Apply window center and width\n    pixel_data = window_image(pixel_data, window_center, window_width)\n\n    ## Rescale the pixel data to the appropriate range for PNG\n    pixel_data = (pixel_data - pixel_data.min()) / (pixel_data.max() - pixel_data.min())\n    pixel_data = (pixel_data * 255).astype(np.uint8)\n\n    return pixel_data\n\ndef window_image(pixel_data, window_center, window_width):\n    \"\"\"\n    Apply window center and width to the pixel data.\n\n    Args:\n        pixel_data (numpy.ndarray): Pixel data from the DICOM file.\n        window_center (int or float): Window center value.\n        window_width (int or float): Window width value.\n\n    Returns:\n        numpy.ndarray: Windowed pixel data.\n    \"\"\"\n    img_min = window_center - window_width // 2\n    img_max = window_center + window_width // 2\n    pixel_data = np.clip(pixel_data, img_min, img_max)\n    pixel_data = (pixel_data - img_min) / (img_max - img_min)\n    return pixel_data\n\ndef get_window_values(ds):\n    \"\"\"\n    Get the window center and window width values from a DICOM file.\n    If the values are sequences, return the first item in the sequence.\n\n    Args:\n        dicom_file (str): Path to the DICOM file.\n\n    Returns:\n        tuple: A tuple containing the window center and window width values.\n    \"\"\"\n\n    ## Get the window center and window width values\n    window_center = ds.WindowCenter\n    window_width = ds.WindowWidth\n\n    ## Handle sequences\n    if isinstance(window_center, (list,tuple)):\n        window_center = window_center[0]\n\n    if isinstance(window_width, (list,tuple)):\n        window_width = window_width[0]\n\n    return window_center, window_width","metadata":{"_kg_hide-input":true,"execution":{"iopub.status.busy":"2024-05-30T17:04:38.967649Z","iopub.execute_input":"2024-05-30T17:04:38.968623Z","iopub.status.idle":"2024-05-30T17:04:38.997060Z","shell.execute_reply.started":"2024-05-30T17:04:38.968589Z","shell.execute_reply":"2024-05-30T17:04:38.995864Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"import pandas as pd\n\ndf = pd.read_csv('/kaggle/input/rsna-2024-lumbar-spine-degenerative-classification/train_label_coordinates.csv')\n\n#coordinates to int\ndf.x = round(df.x)\ndf.y = round(df.y)\n\ndf.x = pd.to_numeric(df.x, downcast='integer')\ndf.y = pd.to_numeric(df.y, downcast='integer')\n\ndf.head()","metadata":{"execution":{"iopub.status.busy":"2024-05-30T17:04:39.000060Z","iopub.execute_input":"2024-05-30T17:04:39.000450Z","iopub.status.idle":"2024-05-30T17:04:39.703780Z","shell.execute_reply.started":"2024-05-30T17:04:39.000418Z","shell.execute_reply":"2024-05-30T17:04:39.702848Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# csv of intensity values for pathology\ntr = pd.read_csv('/kaggle/input/rsna-2024-lumbar-spine-degenerative-classification/train.csv')\ntr.head()","metadata":{"execution":{"iopub.status.busy":"2024-05-30T17:04:39.705353Z","iopub.execute_input":"2024-05-30T17:04:39.706101Z","iopub.status.idle":"2024-05-30T17:04:39.761457Z","shell.execute_reply.started":"2024-05-30T17:04:39.706062Z","shell.execute_reply":"2024-05-30T17:04:39.760451Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# melt data into 3 columns\ndfm = pd.melt(tr, id_vars=['study_id'], \n                    value_vars=tr.columns[1:], \n                    var_name='pathology', \n                    value_name='value')\n\n# create columns to match train_label_coordinates\ndfm['level'] = dfm.pathology.str[-5:]\ndfm['level'] = dfm['level'].str.replace('_', '/').str.upper()\n\ndfm['condition'] = dfm.pathology.str[:-6]\ndfm['condition'] = dfm['condition'].str.replace('_', ' ').str.title()\n\ndel dfm['pathology']\ndfm.head()","metadata":{"execution":{"iopub.status.busy":"2024-05-30T17:04:39.762992Z","iopub.execute_input":"2024-05-30T17:04:39.763355Z","iopub.status.idle":"2024-05-30T17:04:39.935438Z","shell.execute_reply.started":"2024-05-30T17:04:39.763326Z","shell.execute_reply":"2024-05-30T17:04:39.934479Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# merge\ndf = df.merge(dfm)\n\n# get column offset to reconstruct pixels to image\ncolumn_offset = df.shape[1]\nprint(column_offset)\ndf.head()","metadata":{"execution":{"iopub.status.busy":"2024-05-30T17:04:39.936594Z","iopub.execute_input":"2024-05-30T17:04:39.937241Z","iopub.status.idle":"2024-05-30T17:04:40.023175Z","shell.execute_reply.started":"2024-05-30T17:04:39.937205Z","shell.execute_reply":"2024-05-30T17:04:40.021936Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# view one image\nrow = df.iloc[39]\nfn = str(root/str(row.study_id)/str(row.series_id)/f'{row.instance_number}.dcm')\nim = mri_dicom_to_array(fn)\nplt.imshow(im, cmap='gray')","metadata":{"execution":{"iopub.status.busy":"2024-05-30T17:04:40.024442Z","iopub.execute_input":"2024-05-30T17:04:40.024907Z","iopub.status.idle":"2024-05-30T17:04:40.418979Z","shell.execute_reply.started":"2024-05-30T17:04:40.024866Z","shell.execute_reply":"2024-05-30T17:04:40.418025Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# view one patch\noffset = 25\nside = offset*2\n#box = im[row.x-offset:row.x+offset,row.y-offset:row.y+offset]\n(x,y) = im.shape\n\nx1 = max(row.x-offset, 0)\nx2 = min(row.x+offset, x-1)\ny1 = max(row.y-offset, 0)\ny2 = min(row.y+offset, y-1)\nprint(x,y,x1,x2,y1,y2, y2-y1)\n\nif x2 - x1 < side:\n    if x1 == 0:\n         x2 = side\n    else:\n         x1 = x-1-side\n\nif y2 - y1 < side:\n    if y1 == 0:\n         y2 = side\n    else:\n         y1 = y-1-side\n\nbox = im[x1:x2, y1:y2]\nprint(box.shape, x,y,x1,x2,y1,y2)\nplt.imshow(box, cmap='gray')","metadata":{"execution":{"iopub.status.busy":"2024-05-30T17:11:18.772688Z","iopub.execute_input":"2024-05-30T17:11:18.773108Z","iopub.status.idle":"2024-05-30T17:11:19.052166Z","shell.execute_reply.started":"2024-05-30T17:11:18.773076Z","shell.execute_reply":"2024-05-30T17:11:19.051311Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"stacks = []\noffset = 25\nside = offset*2\n\nfor _, row in df.iterrows():\n    # filename \n    fn = str(root/str(row.study_id)/str(row.series_id)/f'{row.instance_number}.dcm')\n    # condition box around x,y center\n    im = mri_dicom_to_array(fn)\n    (x,y) = im.shape\n    x1 = max(row.x-offset, 0)\n    x2 = min(row.x+offset, x-1)\n    y1 = max(row.y-offset, 0)\n    y2 = min(row.y+offset, y-1)\n  \n    if x2 - x1 < side:\n        if x1 == 0:\n             x2 = side\n        else:\n             x1 = x-1-side\n\n    if y2 - y1 < side:\n        if y1 == 0:\n             y2 = side\n        else:\n             y1 = y-1-side\n        \n    box = im[x1:x2, y1:y2]\n    # to list\n    box = np.ravel(box)\n    # add to array\n    stacks.append(box)\n    \n# create stack of all boxes\nstacked_arrays = np.vstack(stacks)\nprint(stacked_arrays.shape)\n\n# dataframe with pixel values\ndff = pd.DataFrame(stacked_arrays)\ndff.head()","metadata":{"execution":{"iopub.status.busy":"2024-05-30T17:12:38.253894Z","iopub.execute_input":"2024-05-30T17:12:38.254312Z","iopub.status.idle":"2024-05-30T17:22:10.588149Z","shell.execute_reply.started":"2024-05-30T17:12:38.254281Z","shell.execute_reply":"2024-05-30T17:22:10.586964Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# add to coordinates df\ndf_boxes = pd.concat([df, dff], axis=1)\ndf_boxes.head()","metadata":{"execution":{"iopub.status.busy":"2024-05-30T16:17:07.466776Z","iopub.status.idle":"2024-05-30T16:17:07.467253Z","shell.execute_reply.started":"2024-05-30T16:17:07.467002Z","shell.execute_reply":"2024-05-30T16:17:07.467054Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"#save\ndf_boxes.to_csv('patches_50px.csv', index=False)","metadata":{"execution":{"iopub.status.busy":"2024-05-30T16:17:07.469577Z","iopub.status.idle":"2024-05-30T16:17:07.470201Z","shell.execute_reply.started":"2024-05-30T16:17:07.469878Z","shell.execute_reply":"2024-05-30T16:17:07.469903Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"## Convert pixels back to image array","metadata":{}},{"cell_type":"code","source":"# sample row\nrow = df_boxes.iloc[10]\n\n#pixels from columns\nrow = row[column_offset:].values.astype('int')\n\n#reshape\nim = row.reshape((50,50))\nplt.imshow(im, cmap='gray')","metadata":{"execution":{"iopub.status.busy":"2024-05-30T14:35:30.234692Z","iopub.execute_input":"2024-05-30T14:35:30.235049Z","iopub.status.idle":"2024-05-30T14:35:30.520773Z","shell.execute_reply.started":"2024-05-30T14:35:30.235019Z","shell.execute_reply":"2024-05-30T14:35:30.519220Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"","metadata":{},"execution_count":null,"outputs":[]}]}