{"metadata":{"kernelspec":{"language":"python","display_name":"Python 3","name":"python3"},"language_info":{"name":"python","version":"3.10.14","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"},{"sourceId":192280709,"sourceType":"kernelVersion"}],"dockerImageVersionId":30761,"isInternetEnabled":false,"language":"python","sourceType":"notebook","isGpuEnabled":false}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"code","source":"!python -m pip install --no-index --find-links=/kaggle/input/download-package-rsna natsort","metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","execution":{"iopub.status.busy":"2024-09-02T01:11:17.776474Z","iopub.execute_input":"2024-09-02T01:11:17.777052Z","iopub.status.idle":"2024-09-02T01:11:35.873756Z","shell.execute_reply.started":"2024-09-02T01:11:17.777004Z","shell.execute_reply":"2024-09-02T01:11:35.871710Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"import pandas as pd \nimport numpy as np\nimport pydicom \nimport tqdm\nimport glob\nfrom natsort import natsorted\nimport cv2","metadata":{"execution":{"iopub.status.busy":"2024-09-02T01:11:35.876826Z","iopub.execute_input":"2024-09-02T01:11:35.877278Z","iopub.status.idle":"2024-09-02T01:11:35.885235Z","shell.execute_reply.started":"2024-09-02T01:11:35.877228Z","shell.execute_reply":"2024-09-02T01:11:35.883780Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"class dotdict(dict):\n    __setattr__ = dict.__setitem__\n    __delattr__ = dict.__delitem__\n\n    def __getattr__(self, name):\n        try:\n            return self[name]\n        except KeyError:\n            raise AttributeError(name)\n\ndataset_dir = '/kaggle/input/rsna-2024-lumbar-spine-degenerative-classification'\n\ndef normalise_to_8bit(x, lower=0.1, upper=99.9): # 1, 99 #0.05, 99.5 #0, 100\n    lower, upper = np.percentile(x, (lower, upper))\n    x = np.clip(x, lower, upper)\n    x = x - np.min(x)\n    x = x / np.max(x)\n    return (x * 255).astype(np.uint8)\n\n# discontinous volume will be splitted to continous chunks\ndef load_and_split_mri_from_dicom_dir(\n        study_id,\n        series_id,\n        series_description,\n    ):\n    dicom_dir= f'{dataset_dir}/test_images/{study_id}/{series_id}'\n\n    #---\n    dicom_file = natsorted(glob.glob( f'{dicom_dir}/*.dcm'))\n    instance_number = [int(f.split('/')[-1].split('.')[0]) for f in dicom_file]\n    dicom = [pydicom.dcmread(f) for f in dicom_file]\n\n    try:\n        dicom_df = []\n        for i,d in zip(instance_number,dicom): #d__.dict__\n            dicom_df.append(\n                dotdict(\n                    study_id=study_id,\n                    series_id=series_id,\n                    series_description=series_description,\n                    instance_number=i,\n                    #InstanceNumber = d.InstanceNumber,\n                    ImagePositionPatient=tuple([float(v) for v in d.ImagePositionPatient]),  # not continous\n                    ImageOrientationPatient=tuple([float(v) for v in d.ImageOrientationPatient]),\n                    PixelSpacing=tuple([float(v) for v in d.PixelSpacing]),\n                    SpacingBetweenSlices=float(d.SpacingBetweenSlices),\n                    SliceThickness=float(d.SliceThickness),\n                )\n            )\n        dicom_df = pd.DataFrame(dicom_df)\n    except Exception as e:\n        return []\n    \n    if dicom_df.empty:\n        return []\n    \n    dicom_df = [d for _,d in dicom_df.groupby('ImageOrientationPatient')]\n\n    #sort slice\n    mri=[]\n    for df in dicom_df:\n        try:\n            position = np.array(df['ImagePositionPatient'].values.tolist())\n            orientation = np.array(df['ImageOrientationPatient'].values.tolist())\n            normal = np.cross(orientation[:,:3], orientation[:,3:])\n            projection = np.sum(normal*position,1) #np.dot(normal, position)\n            df.loc[:,'projection'] = projection\n            df = df.sort_values('projection')\n\n\n            #todo: assert all slices are continous\n            assert len(df.SliceThickness.unique())==1\n            assert len(df.ImageOrientationPatient.unique())==1\n            assert len(df.ImageOrientationPatient.unique())==1\n            assert len(df.SpacingBetweenSlices.unique())==1\n\n            volume =[\n                dicom[instance_number.index(i)].pixel_array for i in df.instance_number\n            ] \n            volume = np.stack(volume)\n            volume = normalise_to_8bit(volume)\n            mri.append(dotdict(\n                df=df,\n                volume=volume,\n            ))\n        except AssertionError:\n            continue\n    mri = []\n    return mri\n\n\n#convert 2d x,y to 3d X,Y,Z for point in label.csv\ndef add_XYZ_to_label_df(study_id_df):\n\n    # add shape W,H; world coords xx,yy,zz\n    for col in ['W','H']:\n        study_id_df.loc[:,col]=0\n    for col in ['xx','yy','zz']:\n        study_id_df.loc[:,col]=0.0\n\n    # for t,d in tqdm.tqdm(study_id_df.iterrows(), total=len(study_id_df)):\n    for t,d in study_id_df.iterrows():\n        #print(d)\n        #print('')\n        dicom_file = f'{dataset_dir}/test_images/{d.study_id}/{d.series_id}/{d.instance_number}.dcm'\n        dicom = pydicom.dcmread(dicom_file)\n        H,W = dicom.pixel_array.shape\n        sx,sy,sz = [float(v) for v in dicom.ImagePositionPatient]\n        o0, o1, o2, o3, o4, o5, = [float(v) for v in dicom.ImageOrientationPatient]\n        delx,dely = dicom.PixelSpacing\n\n        xx =  o0*delx*d.x + o3*dely*d.y + sx\n        yy =  o1*delx*d.x + o4*dely*d.y + sy\n        zz =  o2*delx*d.x + o5*dely*d.y + sz\n\n        study_id_df.loc[t,'W'] = W\n        study_id_df.loc[t,'H'] = H\n        study_id_df.loc[t,'xx'] = xx\n        study_id_df.loc[t,'yy'] = yy\n        study_id_df.loc[t,'zz'] = zz\n    return study_id_df\n\n#read all mri for one patient\ndef load_for_one(study_id_df):\n    study_id = study_id_df['study_id'].values[0]\n    gb = study_id_df.groupby(['series_description','series_id']).agg('first').index #[['series_id', 'series_description']]\n    mri=[]\n    for series_description,series_id in gb:\n        mri += load_and_split_mri_from_dicom_dir(\n            study_id=study_id,\n            series_description=series_description,\n            series_id=series_id\n        )\n    return mri\n\n\n#back project 3D to 2d\ndef backproject_XYZ(xx,yy,zz,mri):\n    r = mri\n    d0 = r.df.iloc[0] \n\n    sx, sy, sz = [float(v) for v in d0.ImagePositionPatient]\n    o0, o1, o2, o3, o4, o5, = [float(v) for v in d0.ImageOrientationPatient]\n    delx, dely = d0.PixelSpacing\n    delz = d0.SpacingBetweenSlices\n\n    ax = np.array([o0,o1,o2])\n    ay = np.array([o3,o4,o5])\n    az = np.cross(ax,ay)\n\n    p = np.array([xx-sx,yy-sy,zz-sz])\n    x = np.dot(ax, p)/delx\n    y = np.dot(ay, p)/dely\n    z = np.dot(az, p)/delz\n    x = int(round(x))\n    y = int(round(y))\n    z = int(round(z))\n\n    D,H,W = r.volume.shape\n    inside = \\\n        (x>=0) & (x<W) &\\\n        (y>=0) & (y<H) &\\\n        (z>=0) & (z<D)\n    if not inside:\n        #print('out-of-bound')\n        return False,0,0,0,0\n\n    n = r.df.instance_number.values[z]\n    return True,x,y,z,n","metadata":{"execution":{"iopub.status.busy":"2024-09-02T01:14:22.736517Z","iopub.execute_input":"2024-09-02T01:14:22.737087Z","iopub.status.idle":"2024-09-02T01:14:22.772505Z","shell.execute_reply.started":"2024-09-02T01:14:22.737032Z","shell.execute_reply":"2024-09-02T01:14:22.771123Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"desc_df = pd.read_csv('/kaggle/input/rsna-2024-lumbar-spine-degenerative-classification/test_series_descriptions.csv').reset_index(drop=True)\ndesc_df","metadata":{"execution":{"iopub.status.busy":"2024-09-02T01:14:22.937367Z","iopub.execute_input":"2024-09-02T01:14:22.937831Z","iopub.status.idle":"2024-09-02T01:14:22.955390Z","shell.execute_reply.started":"2024-09-02T01:14:22.937788Z","shell.execute_reply":"2024-09-02T01:14:22.954062Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# series_list = []\n# for study_id, study_df in desc_df.groupby('study_id'):\n#     description_list = study_df['series_description'].unique()\n# #     print(len(study_df[study_df['series_description'] == 'Axial T2']))\n#     if all([desc in description_list for desc in ['Axial T2', 'Sagittal T2/STIR']]):\n# #         print(study_df.loc[study_df['series_description'] == 'Axial T2', 'series_id'].values[0])\n#         series_list.extend(study_df.loc[study_df['series_description'] == 'Axial T2', 'series_id'].values)\n#         series_list.append(study_df.loc[study_df['series_description'] == 'Sagittal T2/STIR', 'series_id'].values[0])\n# load_desc = desc_df[desc_df['series_id'].isin(series_list)]\n# load_desc = load_desc.sort_values(['study_id', 'series_description'])\n# load_desc","metadata":{"execution":{"iopub.status.busy":"2024-09-02T01:14:23.132294Z","iopub.execute_input":"2024-09-02T01:14:23.132787Z","iopub.status.idle":"2024-09-02T01:14:23.139777Z","shell.execute_reply.started":"2024-09-02T01:14:23.132738Z","shell.execute_reply":"2024-09-02T01:14:23.138067Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"sub_df = []\ncondition_list = [\n    'left_neural_foraminal_narrowing',\n    'left_subarticular_stenosis',\n    'right_neural_foraminal_narrowing',\n    'right_subarticular_stenosis',\n    'spinal_canal_stenosis'\n]\nlevel_list = [\n    'l1_l2',\n    'l2_l3',\n    'l3_l4',\n    'l4_l5',\n    'l5_s1'\n]\nfor study_id in desc_df['study_id'].unique():\n    for condition in condition_list:\n        for level in level_list:\n            sub_df.append(\n                {\n                    'row_id': f'{study_id}_{condition}_{level}',\n                    'normal_mild': 1 / 3,\n                    'moderate': 1 / 3,\n                    'severe': 1 / 3\n                }\n            )\nsub_df = pd.DataFrame(sub_df)","metadata":{"execution":{"iopub.status.busy":"2024-09-02T01:14:23.407337Z","iopub.execute_input":"2024-09-02T01:14:23.408018Z","iopub.status.idle":"2024-09-02T01:14:23.418882Z","shell.execute_reply.started":"2024-09-02T01:14:23.407961Z","shell.execute_reply":"2024-09-02T01:14:23.417060Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# for study_id, study_df in desc_df.groupby('study_id'):\n#     mris = load_for_one(study_df)","metadata":{"execution":{"iopub.status.busy":"2024-09-02T01:14:23.825380Z","iopub.execute_input":"2024-09-02T01:14:23.825834Z","iopub.status.idle":"2024-09-02T01:14:23.832658Z","shell.execute_reply.started":"2024-09-02T01:14:23.825791Z","shell.execute_reply":"2024-09-02T01:14:23.830954Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"for i in tqdm.tqdm(range(len(desc_df))):\n    row = desc_df.loc[i]\n    mris = load_and_split_mri_from_dicom_dir(row['study_id'],\n                                      row['series_id'],\n                                      row['series_description'])","metadata":{"execution":{"iopub.status.busy":"2024-09-02T01:14:24.239996Z","iopub.execute_input":"2024-09-02T01:14:24.240459Z","iopub.status.idle":"2024-09-02T01:14:24.417404Z","shell.execute_reply.started":"2024-09-02T01:14:24.240416Z","shell.execute_reply":"2024-09-02T01:14:24.415859Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"sub_df.to_csv('./submission.csv', index=False)","metadata":{"execution":{"iopub.status.busy":"2024-09-02T01:11:36.332832Z","iopub.execute_input":"2024-09-02T01:11:36.333893Z","iopub.status.idle":"2024-09-02T01:11:36.349036Z","shell.execute_reply.started":"2024-09-02T01:11:36.333830Z","shell.execute_reply":"2024-09-02T01:11:36.347631Z"},"trusted":true},"execution_count":null,"outputs":[]}]}