{"metadata":{"kernelspec":{"language":"python","display_name":"Python 3","name":"python3"},"language_info":{"pygments_lexer":"ipython3","nbconvert_exporter":"python","version":"3.6.4","file_extension":".py","codemirror_mode":{"name":"ipython","version":3},"name":"python","mimetype":"text/x-python"}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"markdown","source":"# Dataset for RNSA 2023 competition : 5mm slices\n\nGenerate a dataset composed of files corresponding to each patient serie scan in the train directory /kaggle/input/rsna-2023-abdominal-trauma-detection/train_images.\n\nOriginal dataset contains dicom files for each scan in patient serie. Those dicom files corresponds to slices which thickness that is different from one patient to another. This dataset standardize it to 5mm slices.\n\nCode relies on dicomsdl+pytorch to go faster than pydicom+scipy. Dicomdsl is for reading the dicom files and pytorch to perform interpolation.\n\n## Run info\n\nRan in 6 hours and 24 minutes with :\n\n    SIZE = (128, 128)\n    THICKNESS_GOAL = 5.\n    NB_PATIENTS = 3147\n\n---","metadata":{}},{"cell_type":"code","source":"! pip install -U dicomsdl","metadata":{"execution":{"iopub.status.busy":"2023-09-27T10:11:04.272114Z","iopub.execute_input":"2023-09-27T10:11:04.27302Z","iopub.status.idle":"2023-09-27T10:11:19.655479Z","shell.execute_reply.started":"2023-09-27T10:11:04.272983Z","shell.execute_reply":"2023-09-27T10:11:19.654269Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"import pandas as pd\nimport cv2\nimport dicomsdl\nimport numpy as np\nfrom pathlib import Path\nfrom tqdm.notebook import tqdm\nimport torch\nimport torch.nn.functional as F\nfrom torchvision import transforms\nfrom torchvision.transforms import InterpolationMode","metadata":{"execution":{"iopub.status.busy":"2023-09-27T10:11:19.661169Z","iopub.execute_input":"2023-09-27T10:11:19.662141Z","iopub.status.idle":"2023-09-27T10:11:22.239566Z","shell.execute_reply.started":"2023-09-27T10:11:19.662096Z","shell.execute_reply":"2023-09-27T10:11:22.238557Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# Config\nBASE_PATH = \"/kaggle/input/rsna-2023-abdominal-trauma-detection\"\nSIZE = (128, 128)\nTHICKNESS_GOAL = 5.\nNB_PATIENTS = 3147","metadata":{"execution":{"iopub.status.busy":"2023-09-27T10:11:22.241161Z","iopub.execute_input":"2023-09-27T10:11:22.241728Z","iopub.status.idle":"2023-09-27T10:11:22.247502Z","shell.execute_reply.started":"2023-09-27T10:11:22.241692Z","shell.execute_reply":"2023-09-27T10:11:22.246425Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# Select GPU if possible\ndevice = torch.device(\"cuda:0\" if torch.cuda.is_available() else \"cpu\")\ndevice","metadata":{"execution":{"iopub.status.busy":"2023-09-27T10:11:22.25051Z","iopub.execute_input":"2023-09-27T10:11:22.250859Z","iopub.status.idle":"2023-09-27T10:11:22.284708Z","shell.execute_reply.started":"2023-09-27T10:11:22.250824Z","shell.execute_reply":"2023-09-27T10:11:22.283757Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# Load train tags\n# Contain slice thickness info for all series\ntrain_tags = pd.read_parquet(f'{BASE_PATH}/train_dicom_tags.parquet')\n# Make series id directly accessible  \ntrain_tags[\"SerieID\"] = train_tags.SeriesInstanceUID.str.split(\".\", expand=True).loc[:,8]","metadata":{"execution":{"iopub.status.busy":"2023-09-27T10:11:22.286411Z","iopub.execute_input":"2023-09-27T10:11:22.287041Z","iopub.status.idle":"2023-09-27T10:11:34.30018Z","shell.execute_reply.started":"2023-09-27T10:11:22.287003Z","shell.execute_reply":"2023-09-27T10:11:34.299155Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"def open_dicom_image(image_path, dsize=SIZE):\n    \"\"\"\n    Open and preprocess a DICOM image.\n\n    Args:\n        image_path (str): The file path to the DICOM image.\n        dsize (tuple): The desired output image size (height, width).\n\n    Returns:\n        numpy.ndarray: The preprocessed DICOM image as a NumPy array.\n    \"\"\"\n    # Open the DICOM image file using dicomsdl\n    dcm_file = dicomsdl.open(image_path)\n    \n    # Retrieve information about the pixel data and windowing\n    info = dcm_file.getPixelDataInfo() # rescaling it taken care of in dicomsdl\n    \n    # Calculate clipping parameters\n    center = int(info[\"WindowCenter\"])\n    width = int(info[\"WindowWidth\"])\n    low = center - width / 2\n    high = center + width / 2\n    \n    # Retrieve and clip the pixel data\n    image = torch.from_numpy(dcm_file.pixelData()).to(device)\n    image = torch.clip(image, low, high)\n    \n    # Normalize the pixel values to the range [0, 1]\n    image = (image - image.min()) / (image.max() - image.min())\n\n    # Invert pixel values for MONOCHROME1 images\n    if info['PhotometricInterpretation'] == \"MONOCHROME1\":\n        image = 1 - image\n\n    # Resize the image to the specified dimensions\n    resize_transform = transforms.Resize(size=SIZE, interpolation=InterpolationMode.BILINEAR, antialias=True)\n    data = resize_transform(torch.unsqueeze(image, dim=0))\n    data = data * 255\n    return data","metadata":{"execution":{"iopub.status.busy":"2023-09-27T10:11:34.301542Z","iopub.execute_input":"2023-09-27T10:11:34.301921Z","iopub.status.idle":"2023-09-27T10:11:34.311416Z","shell.execute_reply.started":"2023-09-27T10:11:34.301885Z","shell.execute_reply":"2023-09-27T10:11:34.310356Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# List all patient folders in the base directory\npatients_path = Path(f\"{BASE_PATH}/train_images\")\n# Iterate through each patient folder\nfor patient_path in tqdm(patients_path.iterdir(), desc=\"Processing patients\", total=NB_PATIENTS): \n    patient = patient_path.name\n    # Iterate through each serie folder of a patient\n    for serie_path in patient_path.iterdir():\n        serie = serie_path.name\n        # Determine the slice thickness for the current patient and series\n        thickness = train_tags.loc[(train_tags[\"PatientID\"] == f\"{patient}\") & (train_tags[\"SerieID\"] == f\"{serie}\"), \"SliceThickness\"].unique()[0]\n        # Calculate the scaling factor based on the desired slice thickness goal\n        factor = thickness/THICKNESS_GOAL\n        # Load and preprocess DICOM images for the current series\n        scans = [open_dicom_image(str(dcm_path)) for dcm_path in serie_path.iterdir()]\n        # Concatenate the DICOM images along the specified dimension (dim=0)\n        scans_3D = torch.cat(scans, dim=0)\n        # Downscale the 3D scans based on the calculated factor using trilinear interpolation\n        scans_3D_5mm = F.interpolate(scans_3D.unsqueeze(0).unsqueeze(0), scale_factor=(factor,1,1), mode='trilinear')\n        # Squeeze the extra dimensions and convert to uint8 data type\n        scans_3D_5mm = scans_3D_5mm.squeeze().to(torch.uint8)\n        # Create a directory for the current patient if it doesn't exist\n        path = Path(f\"/kaggle/working/{patient}\")\n        path.mkdir(exist_ok=True)\n        # Save the preprocessed 3D scans as a PyTorch tensor\n        torch.save(scans_3D_5mm, f=f\"{path}/{serie}.pt\")","metadata":{"execution":{"iopub.status.busy":"2023-09-27T10:11:34.312716Z","iopub.execute_input":"2023-09-27T10:11:34.313693Z","iopub.status.idle":"2023-09-27T11:32:08.156611Z","shell.execute_reply.started":"2023-09-27T10:11:34.313656Z","shell.execute_reply":"2023-09-27T11:32:08.154956Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"","metadata":{},"execution_count":null,"outputs":[]}]}