{"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":71549,"databundleVersionId":8561470,"sourceType":"competition"},{"sourceId":1421668,"sourceType":"datasetVersion","datasetId":832340},{"sourceId":4721425,"sourceType":"datasetVersion","datasetId":2726166},{"sourceId":5470061,"sourceType":"datasetVersion","datasetId":3159441},{"sourceId":8941571,"sourceType":"datasetVersion","datasetId":2292106},{"sourceId":9126231,"sourceType":"datasetVersion","datasetId":5480747},{"sourceId":9212737,"sourceType":"datasetVersion","datasetId":3118989,"isSourceIdPinned":true},{"sourceId":9212962,"sourceType":"datasetVersion","datasetId":5309490,"isSourceIdPinned":true},{"sourceId":9562515,"sourceType":"datasetVersion","datasetId":5141507},{"sourceId":9562564,"sourceType":"datasetVersion","datasetId":5716874},{"sourceId":9575405,"sourceType":"datasetVersion","datasetId":5836645},{"sourceId":9586974,"sourceType":"datasetVersion","datasetId":5310342},{"sourceId":126632540,"sourceType":"kernelVersion"},{"sourceId":181280574,"sourceType":"kernelVersion"},{"sourceId":181722791,"sourceType":"kernelVersion"},{"sourceId":192931945,"sourceType":"kernelVersion"},{"sourceId":193148815,"sourceType":"kernelVersion"},{"sourceId":196461391,"sourceType":"kernelVersion"},{"sourceId":198924449,"sourceType":"kernelVersion"},{"sourceId":199903764,"sourceType":"kernelVersion"},{"sourceId":199954278,"sourceType":"kernelVersion"}],"dockerImageVersionId":30733,"isInternetEnabled":false,"language":"python","sourceType":"notebook","isGpuEnabled":true}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"markdown","source":"## cropped images - axial T2","metadata":{}},{"cell_type":"markdown","source":"# Stage 1","metadata":{}},{"cell_type":"code","source":"%%capture\n!pip install '/kaggle/input/rsna2024-demo-workflow/natsort-8.4.0-py3-none-any.whl'\n!pip install /kaggle/input/notebook0248a0e3f9/pycocotools-2.0.7-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl /kaggle/input/notebook0248a0e3f9/dicomsdl-0.109.3-cp310-cp310-manylinux_2_12_x86_64.manylinux2010_x86_64.whl /kaggle/input/notebook0248a0e3f9/loguru-0.7.2-py3-none-any.whl /kaggle/input/notebook0248a0e3f9/pydicom-2.4.4-py3-none-any.whl /kaggle/input/notebook0248a0e3f9/python_gdcm-3.0.24.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl  /kaggle/input/notebook97381653b4/ensemble_boxes-1.0.9-py3-none-any.whl\n\n!pip install -qq /kaggle/working/python-packages/timm-0.9.10-py3-none-any.whl","metadata":{"_kg_hide-input":true,"execution":{"iopub.status.busy":"2025-09-07T09:29:59.348003Z","iopub.execute_input":"2025-09-07T09:29:59.34855Z","iopub.status.idle":"2025-09-07T09:31:57.626412Z","shell.execute_reply.started":"2025-09-07T09:29:59.348497Z","shell.execute_reply":"2025-09-07T09:31:57.625375Z"},"trusted":true},"outputs":[],"execution_count":1},{"cell_type":"markdown","source":"# 1, axial level estimation by hengck23's code\nref: https://www.kaggle.com/code/hengck23/ver-1-demo-workflow-2-stage-approach?scriptVersionId=191553260","metadata":{}},{"cell_type":"code","source":"from glob import glob\ndebug = False\ndebug_fold = 2\nplot = False\ntrain_test = 'test'\n\nimport sys, os\nsys.path.append('/kaggle/input/rsna2024-demo-workflow')\n\nfrom _dir_setting_ import *\n\nimport matplotlib\nimport matplotlib.pyplot as plt\n\nfrom helper import *\nfrom data import *\nfrom model import *\n\n# STEP0 : SETUP DATA ==========================\n\ncfg=dotdict(\n    point_net=dotdict(\n        checkpoint='/kaggle/input/rsna2024-demo-workflow/00002484.pth',\n        image_size=160,\n    ),\n)\n\nlevel_to_label={\n    'l1_l2':1,\n    'l2_l3':2,\n    'l3_l4':3,\n    'l4_l5':4,\n    'l5_s1':5,\n}\n\n#################################################\n\n# study id used for demo\nid_df = pd.read_csv(f'{DATA_KAGGLE_DIR}/test_series_descriptions.csv')    \n\npoint_net = Net(pretrained=False)\nf = torch.load(cfg.point_net.checkpoint, map_location=lambda storage, loc: storage)\nstate_dict = f['state_dict']\npoint_net.load_state_dict(state_dict, strict=False)\npoint_net.cuda()\npoint_net.eval()\npoint_net.output_type = ['infer']\n\n\nfrom _dir_setting_ import *\nfrom natsort import natsorted\nimport pandas as pd\npd.set_option('mode.chained_assignment', None) # disable SettingWithCopyWarning\n\nimport numpy as np\nimport pydicom\nimport glob\nimport timm\nimport cv2\n\nfrom matplotlib.patches import FancyArrowPatch\nfrom mpl_toolkits.mplot3d import proj3d\nfrom pdb import set_trace as st\n\nclass 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\n################################################################################3\n\n\n# read into volume (np_array) + dicom_header(df)\n'''\nimport notes:\n- instance_number may not be sequentially (can have missing num)\n- instance_number is 1-indexed\n\n'''\n\n## 3d/2d processing #########################################################\ndef np_dot(a,b):\n    return np.sum(a * b, 1)\n\ndef project_to_3d(x,y,z, df):\n    d = df.iloc[z]\n    H, W = d.H, d.W\n    sx, sy, sz = [float(v) for v in d.ImagePositionPatient]\n    o0, o1, o2, o3, o4, o5, = [float(v) for v in d.ImageOrientationPatient]\n    delx, dely = d.PixelSpacing\n\n    xx = o0 * delx * x + o3 * dely * y + sx\n    yy = o1 * delx * x + o4 * dely * y + sy\n    zz = o2 * delx * x + o5 * dely * y + sz\n    return xx,yy,zz\n\n## read data #########################################################\ndef resize_volume(volume, image_size):\n    image = volume.copy()\n    image = np.ascontiguousarray(image.transpose((1, 2, 0)))\n    image = cv2.resize(image, (image_size, image_size), interpolation=cv2.INTER_LINEAR)\n    image = np.ascontiguousarray(image.transpose((2, 0, 1)))  # cv2.INTER_LINEAR=1\n    return image\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)\ndef read_series(study_id,series_id,series_description):\n    data_kaggle_dir = DATA_KAGGLE_DIR\n    dicom_dir = f'{data_kaggle_dir}/{train_test}_images/{study_id}/{series_id}'\n\n    # read dicom file\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    # make dicom header df\n    H, W = dicom[0].pixel_array.shape\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=[float(v) for v in d.ImagePositionPatient],\n                ImageOrientationPatient=[float(v) for v in d.ImageOrientationPatient],\n                PixelSpacing=[float(v) for v in d.PixelSpacing],\n                SpacingBetweenSlices=float(d.SpacingBetweenSlices),\n                SliceThickness=float(d.SliceThickness),\n                grouping=str([round(float(v), 3) for v in d.ImageOrientationPatient]),\n                H=H,\n                W=W,\n            )\n        )\n    dicom_df = pd.DataFrame(dicom_df)\n\n    # sort slices\n    dicom_df = [d for _, d in dicom_df.groupby('grouping')]\n\n    data = []\n    sort_data_by_group = []\n    for df in dicom_df:\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_dot(normal, position)\n        df.loc[:, 'projection'] = projection\n        df = df.sort_values('projection')\n\n\n        # todo: assert all slices are continous ??\n        # use  (position[-1]-position[0])/N = SpacingBetweenSlices ??\n        assert len(df.SliceThickness.unique()) == 1\n        assert len(df.SpacingBetweenSlices.unique()) == 1\n\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        data.append(dotdict(\n            df=df,\n            volume=volume,\n        ))\n\n        if 'sagittal' in series_description.lower():\n            sort_data_by_group.append(position[0, 0])  # x\n        if 'axial' in series_description.lower():\n            sort_data_by_group.append(position[0, 2])  # z\n\n    data = [r for _, r in sorted(zip(sort_data_by_group, data))]\n    for i, r in enumerate(data):\n        r.df.loc[:, 'group'] = i\n\n    df = pd.concat([r.df for r in data])\n    df.loc[:, 'z'] = np.arange(len(df))\n    try:    \n        volume = np.concatenate([r.volume for r in data])\n    except:\n        volume = []\n        for r in data:\n            # st()\n            volume.append([cv2.resize(im, (608, 608)) for im in r.volume])\n        volume = np.concatenate(volume)\n    data = dotdict(\n        series_id=series_id,\n        df=df,\n        volume=volume,\n    )\n    return data\ndef read_axial_df(study_id,series_id,series_description):\n    data_kaggle_dir = DATA_KAGGLE_DIR\n    dicom_dir = f'{data_kaggle_dir}/{train_test}_images/{study_id}/{series_id}'\n\n    # read dicom file\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    # make dicom header df\n    H, W = dicom[0].pixel_array.shape\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=[float(v) for v in d.ImagePositionPatient],\n                ImageOrientationPatient=[float(v) for v in d.ImageOrientationPatient],\n                PixelSpacing=[float(v) for v in d.PixelSpacing],\n                SpacingBetweenSlices=float(d.SpacingBetweenSlices),\n                SliceThickness=float(d.SliceThickness),\n                grouping=str([round(float(v), 3) for v in d.ImageOrientationPatient]),\n                H=H,\n                W=W,\n            )\n        )\n    dicom_df = pd.DataFrame(dicom_df)\n\n    # sort slices\n    dicom_df = [d for _, d in dicom_df.groupby('grouping')]\n\n    data = []\n    sort_data_by_group = []\n    for df in dicom_df:\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_dot(normal, position)\n        df.loc[:, 'projection'] = projection\n        df = df.sort_values('projection')\n\n\n        # todo: assert all slices are continous ??\n        # use  (position[-1]-position[0])/N = SpacingBetweenSlices ??\n        assert len(df.SliceThickness.unique()) == 1\n        if len(df.SpacingBetweenSlices.unique()) != 1:\n            import pdb;pdb.set_trace()\n        assert len(df.SpacingBetweenSlices.unique()) == 1\n\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        data.append(dotdict(\n            df=df,\n            volume=volume,\n        ))\n\n        if 'sagittal' in series_description.lower():\n            sort_data_by_group.append(position[0, 0])  # x\n        if 'axial' in series_description.lower():\n            sort_data_by_group.append(position[0, 2])  # z\n\n    data = [r for _, r in sorted(zip(sort_data_by_group, data))]\n    for i, r in enumerate(data):\n        r.df.loc[:, 'group'] = i\n\n    df = pd.concat([r.df for r in data])\n    df.loc[:, 'z'] = np.arange(len(df))\n    return df\n\ndef read_study(study_id, sagittal_t2_id, axial_t2_id):\n    return dotdict(\n        study_id = study_id,\n        sagittal_t2 =read_series(study_id, sagittal_t2_id, 'sagittal_t2'),\n        axial_t2 =read_series(study_id, axial_t2_id, 'axial_t2'),\n    )\ndef get_true_sagittal_t2_point(study_id, sagittal_t2_df):\n    series_id = sagittal_t2_df.iloc[0].series_id\n    label_coord_df = pd.read_csv(f'{DATA_KAGGLE_DIR}/{train_test}_label_coordinates.csv')\n    label_df = label_coord_df[\n         (label_coord_df.study_id == study_id)\n       & (label_coord_df.series_id == series_id)\n    ]\n    label_df = label_df.sort_values('level')\n    point=label_df[['x','y']].values\n    instance_number = label_df.instance_number.values\n\n    #mapping from instance num to z (array index)\n    map_instance_number, map_z = sagittal_t2_df[['instance_number','z',]].values.T\n    map = {n:z for n,z in zip(map_instance_number,map_z)}\n    z = [map[n] for n in instance_number]\n    return point,z\n\n############################################################3\n#post processing (sagittal_t2 point net)\ndef probability_to_point(probability, threshold=0.5):\n    #todo: handle mssing point\n    point=[]\n    for l in range(1, 6):\n        y, x = np.where(probability[l] > threshold)\n        y = round(y.mean())\n        x = round(x.mean())\n        point.append((x, y))\n    return point\n\n\ndef view_to_world(sagittal_t2_point, z, sagittal_t2_df, image_size):\n\n    H = sagittal_t2_df.iloc[0].H\n    W = sagittal_t2_df.iloc[0].W\n    scale_x = W / image_size\n    scale_y = H / image_size\n\n    xxyyzz = []\n    for l in range(1, 6):\n        x,y = sagittal_t2_point[l-1]\n        xx,yy,zz = project_to_3d(x*scale_x, y*scale_y, z, sagittal_t2_df)\n        xxyyzz.append((xx, yy, zz))\n\n    xxyyzz = np.array(xxyyzz)\n    return xxyyzz\n\ndef point_to_level(world_point, axial_t2_df):\n\n    # we get closest axial slices (z) to the CSC world points\n\n    xxyyzz = world_point\n    orientation = np.array(axial_t2_df.ImageOrientationPatient.values.tolist())\n    position = np.array(axial_t2_df.ImagePositionPatient.values.tolist())\n    ox = orientation[:, :3]\n    oy = orientation[:, 3:]\n    oz = np.cross(ox, oy)\n    t = xxyyzz.reshape(-1, 1, 3) - position.reshape(1, -1, 3)\n    dis = (oz.reshape(1, -1, 3) * t).sum(-1)  # np.dot(point-s,oz)\n    fdis = np.fabs(dis)\n    closest_z = fdis.argmin(-1)\n    closest_fdis = fdis.min(-1)\n    closest_df = axial_t2_df.iloc[closest_z]\n\n    if 1:\n        #<todo> hard/soft assigment, multi/single assigment\n        # no assignment based on distance\n\n        # allow point found in multi group\n        num_group   = len(axial_t2_df['group'].unique())\n        point_group = axial_t2_df.group.values[fdis.argsort(-1)[:, :3]].tolist()\n        point_group = [list(set(g)) for g in point_group]\n        group_point = [[] for g in range(num_group)]\n        for l in range(5):\n            for k in point_group[l]:\n                group_point[k].append(l)\n                # print(k)\n                # print(group_point[k])\n                # print(group_point)\n        group_point = [sorted(list(set(g))) for g in group_point]\n\n    D = len(axial_t2_df)\n    assigned_level=np.full(D,fill_value=0, dtype=int)\n    for group in range(num_group):\n        point_in_this_group = np.array(group_point[group])  # np.where(closest_df['group'] == group)[0]\n        slice_in_this_group = np.where(axial_t2_df['group'] == group)[0]\n        if len(point_in_this_group) == 0:\n            continue # unassigned, level=0\n\n        level = point_in_this_group[fdis[point_in_this_group][:, slice_in_this_group].argmin(0)] + 1\n        assigned_level[slice_in_this_group] = level\n\n    # sor =  (fdis.argmin(0)+1)[closest_z]\n    # closest_z= [ fdis.argmin(0)+1]\n    return assigned_level, closest_z, closest_fdis #dis is soft assignment\n\n\n#########################################################################3\n#visualisation\nlevel_color = [\n    [0, 0, 0],\n    [255, 0, 0],\n    [0, 255, 0],\n    [0, 0, 255],\n    [255, 255, 0],\n    [0, 255, 255],\n]\n\ndef probability_to_rgb(probability):\n    _6_,H,W = probability.shape\n    rgb = np.zeros((H, W, 3))\n    for i in range(1, 6):\n        rgb += probability[i].reshape(H, W, 1) * [[level_color[i]]]\n    rgb = rgb.astype(np.uint8)\n    return rgb\n\n\nclass Arrow3D(FancyArrowPatch):\n    def __init__(self, xs, ys, zs, *args, **kwargs):\n        super().__init__((0,0), (0,0), *args, **kwargs)\n        self._verts3d = xs, ys, zs\n\n    def do_3d_projection(self, renderer=None):\n        xs3d, ys3d, zs3d = self._verts3d\n        xs, ys, zs = proj3d.proj_transform(xs3d, ys3d, zs3d, self.axes.M)\n        self.set_positions((xs[0],ys[0]),(xs[1],ys[1]))\n        return np.min(zs)\n\ndef draw_slice(\n    ax, df,\n    is_slice =True, scolor=[[1,0,0]], salpha=[0.1],\n    is_border=True, bcolor=[[1,0,0]], balpha=[0.1],\n    is_origin=True, ocolor=[[1,0,0]], oalpha=[0.1],\n    is_arrow=True,\n):\n    df = df.copy()\n    df = df.reset_index(drop=True)\n\n    D = len(df)\n    if len(scolor)==1: scolor = scolor*D\n    if len(salpha)==1: salpha = salpha*D\n    if len(bcolor)==1: bcolor = bcolor*D\n    if len(balpha)==1: balpha = balpha*D\n    if len(ocolor)==1: ocolor = bcolor*D\n    if len(oalpha)==1: oalpha = balpha*D\n\n\n    #for i,d in df.iterrows():\n    for i in range(D):\n        d = df.iloc[i]\n        W, H = d.W, d.H\n        o0, o1, o2, o3, o4, o5 = d.ImageOrientationPatient\n        ox = np.array([o0, o1, o2])\n        oy = np.array([o3, o4, o5])\n        sx, sy, sz = d.ImagePositionPatient\n        s = np.array([sx, sy, sz])\n        delx, dely = d.PixelSpacing\n\n        p0 = s\n        p1 = s + W * delx * ox\n        p2 = s + H * dely * oy\n        p3 = s + H * dely * oy + W * delx * ox\n\n        grid = np.stack([p0, p1, p2, p3]).reshape(2, 2, 3)\n        gx = grid[:, :, 0]\n        gy = grid[:, :, 1]\n        gz = grid[:, :, 2]\n\n        #outline\n        if is_slice:\n            ax.plot_surface(gx, gy, gz, color=scolor[i], alpha=salpha[i])\n\n        if is_border:\n            line = np.stack([p0, p1, p3, p2] )\n            ax.plot(line[:,0], line[:,1], zs=line[:,2], color=ocolor[i], alpha=oalpha[i])\n\n        if is_origin:\n            ax.scatter([sx], [sy], [sz],   color=ocolor[i], alpha=oalpha[i])\n\n    #check ordering of slice\n    if is_arrow :\n        sx0, sy0, sz0 = df.iloc[0].ImagePositionPatient\n        sx1, sy1, sz1 = df.iloc[-1].ImagePositionPatient\n        arrow_prop_dict = dict(mutation_scale=20, arrowstyle='-|>', color='k', shrinkA=0, shrinkB=0)\n        a = Arrow3D([sx0, sx1], [sy0, sy1], [sz0, sz1], **arrow_prop_dict)\n        ax.add_artist(a)\n\n\nplot = False        \n\n\nfrom tqdm import tqdm\ndfs = []\nerror_ids = []\nall_dfs = []\nfor study_id_n, study_id in enumerate(tqdm(id_df.study_id.unique())):\n    for axial_t2_id in id_df[(id_df.study_id == study_id) & (id_df.series_description=='Axial T2')].series_id.values:\n        try:\n\n            sagittal_t2_id = id_df[(id_df.study_id == study_id) & (id_df.series_description=='Sagittal T2/STIR')].iloc[0].series_id\n            data = read_study(study_id, axial_t2_id=axial_t2_id, sagittal_t2_id=sagittal_t2_id)\n\n\n            #--- step.1 : detect 2d point in sagittal_t2\n            sagittal_t2 = data.sagittal_t2.volume\n            sagittal_t2_df = data.sagittal_t2.df\n            axial_t2_df = data.axial_t2.df\n            all_dfs.append(axial_t2_df)\n\n            D,H,W = sagittal_t2.shape\n            image = resize_volume(sagittal_t2,cfg.point_net.image_size)\n\n            sagittal_t2_z = D//2\n            image = image[sagittal_t2_z] #we use only center image #todo: better selection\n\n            batch = dotdict(\n                sagittal=torch.from_numpy(image).unsqueeze(0).unsqueeze(0).byte()\n            )\n            with torch.cuda.amp.autocast(enabled=True):\n                with torch.no_grad():\n                    output = point_net(batch)\n\n            probability = output['probability'][0].float().data.cpu().numpy()\n            sagittal_t2_point = probability_to_point(probability) #5 level 2d points, todo: check invalid output point\n\n#             #for debug and development\n#             point_hat, z_hat = sagittal_t2_point_hat = get_true_sagittal_t2_point(study_id, sagittal_t2_df)\n#             point_hat = point_hat*[[cfg.point_net.image_size/W, cfg.point_net.image_size/H]]\n\n\n            #--- step.2 : perdict slice level of axial_t2\n            world_point = world_point = view_to_world(sagittal_t2_point, sagittal_t2_z, sagittal_t2_df, cfg.point_net.image_size)\n            assigned_level, closest_z, closest_fdis = axial_t2_level = point_to_level(world_point, axial_t2_df)\n            \n            axial_t2_df['level'] = assigned_level\n#             break\n            exist_closest_fdis = closest_fdis[np.unique([c for c in assigned_level if c != 0])-1]\n            exist_closest_z = closest_z[np.unique([c for c in assigned_level if c != 0])-1]\n            ns = axial_t2_df.iloc[exist_closest_z].instance_number    \n            axial_t2_df.loc[~axial_t2_df.instance_number.isin(ns), 'closest'] = 0\n            axial_t2_df.loc[axial_t2_df.instance_number.isin(ns), 'closest'] = 1\n            axial_t2_df['closest'] = axial_t2_df['closest'].astype(int)\n            for level in range(1, 6):\n                axial_t2_df.loc[axial_t2_df.level == level, 'dis'] = closest_fdis[level-1]            \n                \n            for n, dis in zip(ns, exist_closest_fdis):\n                axial_t2_df.loc[axial_t2_df.instance_number == n, 'dis'] = dis\n                \n            assert len(assigned_level)==len(axial_t2_df)\n            dfs.append(axial_t2_df)        \n\n\n\n            if plot:\n#             if exist_closest_fdis.max() > 5:                \n                print(axial_t2_id, exist_closest_fdis.max(), assigned_level)\n                ###################################################################\n                #visualisation\n\n                # https://matplotlib.org/stable/gallery/mplot3d/mixed_subplots.html\n                fig = plt.figure(figsize=(23, 6))\n                ax1 = fig.add_subplot(1, 1, 1)\n                ax2 = fig.add_subplot(1, 2, 2, projection='3d')\n\n                # detection result\n                p = probability_to_rgb(probability)\n                m = cv2.cvtColor(image, cv2.COLOR_GRAY2BGR)\n                m = 255 - (255 - m * 0.8) * (1 - p / 255)\n\n                ax1.imshow(m / 255)\n                ax1.set_title(f'sagittal keypoint detection (unet)\\n series_id: {axial_t2_id}')\n\n\n                # draw  assigned_level\n                level_ncolor = np.array(level_color) / 255\n                coloring = level_ncolor[assigned_level].tolist()\n                draw_slice(\n                    ax2, axial_t2_df,\n                    is_slice=True,   scolor=coloring, salpha=[0.1],\n                    is_border=True,  bcolor=coloring, balpha=[0.2],\n                    is_origin=False, ocolor=[[0, 0, 0]], oalpha=[0.0],\n                    is_arrow=True\n                )\n\n            #     draw world_point\n                ax2.scatter(world_point[:, 0], world_point[:, 1], world_point[:, 2], alpha=1, color='black')\n\n\n                ### draw closest slice\n                coloring = level_ncolor[1:].tolist()\n                draw_slice(\n                    ax2, axial_t2_df.iloc[closest_z],\n                    is_slice=True, scolor=coloring, salpha=[0.1],\n                    is_border=True, bcolor=coloring, balpha=[1],\n                    is_origin=False, ocolor=[[1, 0, 0]], oalpha=[0],\n                    is_arrow=False\n                )\n\n                ax2.set_aspect('equal')\n                ax2.set_title(f'axial slice assignment\\n series_id:{sagittal_t2_id}')\n                ax2.set_xlabel('x')\n                ax2.set_ylabel('y')\n                ax2.set_zlabel('z')\n                ax2.view_init(elev=0, azim=-10, roll=0)\n                plt.tight_layout(pad=2)\n                plt.show()\n    \n        except:\n            error_ids.append(axial_t2_id)\naxial_closest_df = pd.concat(dfs)\naxial_direction = pd.concat(all_dfs)\naxial_direction.to_csv('axial_direction.csv', index=False)\naxial_closest_df.to_csv('axial_closest_df.csv', index=False)","metadata":{"execution":{"iopub.status.busy":"2025-09-07T09:31:57.628713Z","iopub.execute_input":"2025-09-07T09:31:57.628999Z","iopub.status.idle":"2025-09-07T09:32:13.942375Z","shell.execute_reply.started":"2025-09-07T09:31:57.628975Z","shell.execute_reply":"2025-09-07T09:32:13.941488Z"},"trusted":true},"outputs":[{"name":"stderr","text":"100%|██████████| 1/1 [00:03<00:00,  3.62s/it]\n","output_type":"stream"}],"execution_count":2},{"cell_type":"markdown","source":"# 2, setup","metadata":{}},{"cell_type":"code","source":"import numpy as np # linear algebra\nimport pandas as pd # data processing, CSV file I/O (e.g. pd.read_csv)\nimport os\nfrom glob import glob\nimport albumentations as A\nimport torch\nfrom torch.utils.data import Dataset\nfrom torch.utils.data import DataLoader\nimport copy\nfrom scipy.special import softmax\ndef sigmoid(x):\n    return 1/(1 + np.exp(-x))\nfrom sklearn.metrics import roc_auc_score, confusion_matrix, mean_squared_error, average_precision_score, recall_score\nimport warnings\nwarnings.simplefilter('ignore')\n\nimport os\nimport cv2\nimport gdcm\nimport pydicom\nimport zipfile\nimport seaborn as sns\nimport matplotlib.pyplot as plt\nimport dicomsdl\nfrom pdb import set_trace as st\nfrom tqdm import tqdm\nfrom joblib import Parallel, delayed\nfrom pydicom.pixel_data_handlers.util import apply_voi_lut\nimport torch","metadata":{"execution":{"iopub.status.busy":"2025-09-07T09:32:13.943544Z","iopub.execute_input":"2025-09-07T09:32:13.943812Z","iopub.status.idle":"2025-09-07T09:32:15.168037Z","shell.execute_reply.started":"2025-09-07T09:32:13.94379Z","shell.execute_reply":"2025-09-07T09:32:15.167357Z"},"trusted":true},"outputs":[],"execution_count":3},{"cell_type":"code","source":"def standardize_pixel_array(dcm: pydicom.dataset.FileDataset) -> np.ndarray:\n    \"\"\"\n    Source : https://www.kaggle.com/competitions/rsna-2023-abdominal-trauma-detection/discussion/427217\n    \"\"\"\n    # Correct DICOM pixel_array if PixelRepresentation == 1.\n    pixel_array = dcm.pixel_array\n    if dcm.PixelRepresentation == 1:\n        bit_shift = dcm.BitsAllocated - dcm.BitsStored\n        dtype = pixel_array.dtype \n        new_array = (pixel_array << bit_shift).astype(dtype) >>  bit_shift\n        pixel_array = pydicom.pixel_data_handlers.util.apply_modality_lut(new_array, dcm)\n    return pixel_array\n\ndef get_center_x_path(study_id, series_id):\n    path_x_map = {}\n    for dcm_path in sorted(glob(f\"{dicom_dir}/{study_id}/{series_id}/*.dcm\")):\n        filename = dcm_path.split('/')[-1].replace('.dcm', '')\n    #         if int(instance_number) % 2 == 1:\n    #             continue\n        dicom = dicomsdl.open(dcm_path)\n        pos_x = dicom['ImagePositionPatient'][0]\n        path_x_map[pos_x] = dcm_path\n        xs = []\n        for i, k in enumerate(sorted(path_x_map.keys())):\n            xs.append(k)\n    return path_x_map[xs[len(xs)//2-1]], path_x_map[xs[len(xs)//2]], path_x_map[xs[len(xs)//2+1]]\n\ndef read_sagittal_x_center_dicom(args, verbose=False):\n    study_id, series_id = args\n    ch_imgs = []\n    dcm_path_3 = get_center_x_path(study_id, series_id)\n    for dcm_path in dcm_path_3:\n        dicom = dicomsdl.open(dcm_path)\n        img = dicom.pixelData(storedvalue = True)\n\n        if dicom['PixelRepresentation'] == 1:\n            bit_shift = dicom['BitsAllocated'] - dicom['BitsStored']\n            dtype = img.dtype\n            img = (img << bit_shift).astype(dtype) >>  bit_shift\n        img = img.astype(np.float32)\n\n        intercept = dicom['RescaleIntercept']\n        slope = dicom['RescaleSlope']\n        if (slope is not None) & (intercept is not None):\n            img = img * slope + intercept\n\n        img = (img - img.min()) / (img.max() - img.min() + 1e-6)\n\n        if dicom.PhotometricInterpretation == \"MONOCHROME1\":\n            img = 1 - img\n        img = (img*255.0).astype('uint8')\n\n        ch_imgs.append(img)\n    if verbose:\n        plt.imshow(ch_imgs[1], 'gray')\n        plt.show()\n    img = np.array(ch_imgs).transpose((1,2,0))\n    cv2.imwrite(f'/kaggle/temp/sagittal_all_images/{study_id}___{series_id}.png', img)\n    \n    \n    \ndef select_path_list(lst, N, offset=0, skip=1):\n    if not lst:\n        return [''] * (2 * N + 1)\n    \n    center = (len(lst) // 2) + offset\n    result = [''] * (2 * N + 1)\n    \n    if 0 <= center < len(lst):\n        result[N] = lst[center]\n    \n    for i in range(1, N + 1):\n        left_index = center - i*skip\n        right_index = center + i*skip\n        \n        if 0 <= left_index < len(lst):\n            result[N - i] = lst[left_index]\n        else:\n            result[N - i] = lst[0]\n        if 0 <= right_index < len(lst):\n            result[N + i] = lst[right_index]\n        else:\n            result[N + i] = lst[len(lst)-1]\n    \n    return result\n\n    \ndef read_sagittal_dicom(args, verbose=False):\n    study_id, series_id = args\n    imgs = {}\n    origin_paths = []\n    zs = []\n    xyzs = []\n    paths = []\n    for dcm_path in glob(f\"{dicom_dir}/{study_id}/{series_id}/*.dcm\"):\n        filename = dcm_path.split('/')[-1].replace('.dcm', '')\n        dicom = dicomsdl.open(dcm_path)\n        img = dicom.pixelData(storedvalue = True)\n\n        if dicom['PixelRepresentation'] == 1:\n            bit_shift = dicom['BitsAllocated'] - dicom['BitsStored']\n            dtype = img.dtype\n            img = (img << bit_shift).astype(dtype) >>  bit_shift\n        img = img.astype(np.float32)\n\n        intercept = dicom['RescaleIntercept']\n        slope = dicom['RescaleSlope']\n        if (slope is not None) & (intercept is not None):\n            img = img * slope + intercept\n\n        img = (img - img.min()) / (img.max() - img.min() + 1e-6)\n\n        if dicom.PhotometricInterpretation == \"MONOCHROME1\":\n            img = 1 - img\n        img = (img*255.0).astype('uint8')\n        save_path = f'/kaggle/temp/sagittal_all_images/{study_id}___{series_id}___{filename}.png'\n        paths.append(save_path)\n\n        imgs[save_path] = img\n        origin_paths.append(dcm_path)\n        xyzs.append(dicom['ImagePositionPatient'])\n\n    df = pd.DataFrame({\n        'path': paths,\n        'origin_path': origin_paths,\n    })\n    df[['x_pos', 'y_pos', 'z_pos']] = np.array(xyzs)\n    df['study_id'] = study_id\n    df['series_id'] = series_id\n    df['instance_number'] = df['path'].apply(lambda x: int(x.split('___')[-1].replace('.png', '')))\n    df = df.sort_values(['x_pos', 'instance_number'])\n    df = df.drop_duplicates('x_pos')        \n    path_list = df.path.values\n    for path_n, path in enumerate(df.path):\n        if path_n == 0:\n            prev_path = df.path.values[0]\n        else:\n            prev_path = df.path.values[path_n-1]\n        prev_im = imgs[prev_path]            \n        \n        im = imgs[path]\n        if path_n == len(df)-1:\n            next_path = df.path.values[-1]\n        else:\n            next_path = df.path.values[path_n+1]\n        next_im = imgs[next_path]\n  \n        if not (prev_im.shape == im.shape == next_im.shape):\n            s = prev_im.shape\n            im = cv2.resize(im, s)\n            next_im = cv2.resize(next_im, s)\n\n        image = np.array([prev_im, im, next_im]).transpose((1,2,0))\n        cv2.imwrite(path, image)\n    \n    if verbose:\n        rows = 7\n        for n, k in enumerate(sorted(imgs.keys())):\n            if n % rows == 0:\n                fig = plt.figure(figsize=(20, 20))\n            fig.add_subplot(1, rows, n%rows+1)\n\n            im = imgs[k]\n            plt.imshow(im, 'gray')\n\n            if n % rows == rows-1:\n                plt.show()        \n        \n    return df\n\ndef read_axial_dicom(args, verbose=False):\n    study_id, series_id = args\n    imgs = {}\n    origin_paths = []\n    zs = []\n    xyzs = []\n    paths = []\n    series_axial_direction = axial_direction[axial_direction.series_id==series_id]\n    for dcm_path in glob(f\"{dicom_dir}/{study_id}/{series_id}/*.dcm\"):\n        filename = dcm_path.split('/')[-1].replace('.dcm', '')\n#         if int(instance_number) % 2 == 1:\n#             continue\n        dicom = dicomsdl.open(dcm_path)\n        img = dicom.pixelData(storedvalue = True)\n\n        if dicom['PixelRepresentation'] == 1:\n            bit_shift = dicom['BitsAllocated'] - dicom['BitsStored']\n            dtype = img.dtype\n            img = (img << bit_shift).astype(dtype) >>  bit_shift\n        img = img.astype(np.float32)\n\n        intercept = dicom['RescaleIntercept']\n        slope = dicom['RescaleSlope']\n        if (slope is not None) & (intercept is not None):\n            img = img * slope + intercept\n\n        pos_z = dicom['ImagePositionPatient'][-1]\n        \n        img = (img - img.min()) / (img.max() - img.min() + 1e-6)\n\n        if dicom.PhotometricInterpretation == \"MONOCHROME1\":\n            img = 1 - img\n        img = (img*255.0).astype('uint8')\n        save_path = f'/kaggle/temp/axial_all_images/{study_id}___{series_id}___{filename}.png'\n        paths.append(save_path)\n\n        imgs[save_path] = img\n        origin_paths.append(dcm_path)\n        xyzs.append(dicom['ImagePositionPatient'])\n\n    df = pd.DataFrame({\n        'path': paths,\n        'origin_path': origin_paths,\n    })\n    df[['x_pos', 'y_pos', 'z_pos']] = np.array(xyzs)\n    df['study_id'] = study_id\n    df['series_id'] = series_id\n    df['instance_number'] = df['path'].apply(lambda x: int(x.split('___')[-1].replace('.png', '')))\n\n    l = len(df)\n    if len(df.merge(series_axial_direction[['instance_number', 'z']], on='instance_number')) == l:\n        df = df.merge(series_axial_direction[['instance_number', 'z']], on='instance_number')\n        df = df.sort_values(['z', 'instance_number'])\n    else:\n        df = df.sort_values(['z_pos', 'instance_number'])\n    path_list = df.path.values\n\n    for path_n, path in enumerate(df.path):\n        im = imgs[path]\n        if path_n == 0:\n            prev_path = df.path.values[0]\n        else:\n            prev_path = df.path.values[path_n-1]\n        prev_im = imgs[prev_path]\n        \n        if path_n == len(df)-1:\n            next_path = df.path.values[-1]\n        else:\n            next_path = df.path.values[path_n+1]\n        next_im = imgs[next_path]\n  \n        if not (prev_im.shape == im.shape == next_im.shape):\n\n            s = prev_im.shape\n            im = cv2.resize(im, s)\n            next_im = cv2.resize(next_im, s)\n\n        image = np.array([prev_im, im, next_im]).transpose((1,2,0))\n        cv2.imwrite(path, image)\n\n    if series_id not in axial_closest_df.series_id.unique():\n        for path_n, path in enumerate(df.path):\n            im = imgs[path]\n            if path_n <= 1:\n                prev_path = df.path.values[0]\n            else:\n                prev_path = df.path.values[path_n-2]\n            prev_im = imgs[prev_path]\n            \n            if path_n >= len(df)-2:\n                next_path = df.path.values[-1]\n            else:\n                next_path = df.path.values[path_n+2]\n            next_im = imgs[next_path]\n      \n            if not (prev_im.shape == im.shape == next_im.shape):\n\n                s = prev_im.shape\n                im = cv2.resize(im, s)\n                next_im = cv2.resize(next_im, s)\n\n            image = np.array([prev_im, im, next_im]).transpose((1,2,0))\n            cv2.imwrite(path.replace('.png', '_stride2.png'), image)\n\n        for path_n, path in enumerate(df.path):\n            im = imgs[path]\n            if path_n <= 2:\n                prev_path = df.path.values[0]\n            else:\n                prev_path = df.path.values[path_n-3]\n            prev_im = imgs[prev_path]\n            \n            if path_n >= len(df)-3:\n                next_path = df.path.values[-1]\n            else:\n                next_path = df.path.values[path_n+3]\n            next_im = imgs[next_path]\n      \n            if not (prev_im.shape == im.shape == next_im.shape):\n\n                s = prev_im.shape\n                im = cv2.resize(im, s)\n                next_im = cv2.resize(next_im, s)\n\n            image = np.array([prev_im, im, next_im]).transpose((1,2,0))\n            cv2.imwrite(path.replace('.png', '_stride3.png'), image)\n        \n    if verbose:\n        rows = 7\n        for n, k in enumerate(sorted(imgs.keys())):\n            if n % rows == 0:\n                fig = plt.figure(figsize=(20, 20))\n            fig.add_subplot(1, rows, n%rows+1)\n\n            im = imgs[k]\n            plt.imshow(im, 'gray')\n\n            if n % rows == rows-1:\n                plt.show()        \n        \n    return df\n","metadata":{"execution":{"iopub.status.busy":"2025-09-07T09:32:15.205904Z","iopub.execute_input":"2025-09-07T09:32:15.206264Z","iopub.status.idle":"2025-09-07T09:32:15.239601Z","shell.execute_reply.started":"2025-09-07T09:32:15.206227Z","shell.execute_reply":"2025-09-07T09:32:15.238796Z"},"trusted":true},"outputs":[],"execution_count":5},{"cell_type":"code","source":"DEVICE = torch.device(\"cuda:0\" if torch.cuda.is_available() else \"cpu\")\n\nuse_local_box_when_debug = False\n\nif debug:\n    df = pd.read_csv('/kaggle/input/rsna-2024-lumbar-spine-degenerative-classification/train_series_descriptions.csv')\n    fold_df = pd.read_csv('/kaggle/input/rsna2024-csvs/train_v3.csv')\n    df = df[df.study_id.isin(fold_df[fold_df.fold==debug_fold].study_id)]\n    if short_debug:\n        df = df[df.study_id.isin(use_ids)]\n#         df = df[df.series_id==3636216534]\n\nelse:\n    df = pd.read_csv('/kaggle/input/rsna-2024-lumbar-spine-degenerative-classification/test_series_descriptions.csv')\ndicom_dir = f'/kaggle/input/rsna-2024-lumbar-spine-degenerative-classification/{train_test}_images'\n","metadata":{"execution":{"iopub.status.busy":"2025-09-07T09:32:15.240533Z","iopub.execute_input":"2025-09-07T09:32:15.240752Z","iopub.status.idle":"2025-09-07T09:32:15.25627Z","shell.execute_reply.started":"2025-09-07T09:32:15.240734Z","shell.execute_reply":"2025-09-07T09:32:15.255694Z"},"trusted":true},"outputs":[],"execution_count":6},{"cell_type":"markdown","source":"# dicom to png","metadata":{}},{"cell_type":"code","source":"\nimage_save_dir = '/kaggle/temp/axial_all_images'\nos.makedirs(image_save_dir, exist_ok=True)\n\naxial_df = df[df.series_description == 'Axial T2']\nargs = axial_df.drop_duplicates(['study_id', 'series_id'])[['study_id', 'series_id']].values\n\n\nfrom multiprocessing import Pool\n\np = Pool(processes=4)\nresults = []\nwith tqdm(total=len(args)) as pbar:\n    for res in p.imap(read_axial_dicom, args):\n        results.append(res)\n        pbar.update(1)\np.close()\n\naxial_df = pd.concat(results)\naxial_df.to_csv('axial_df.csv', index=False)\n\nimage_save_dir = '/kaggle/temp/sagittal_all_images'\nos.makedirs(image_save_dir, exist_ok=True)\n\nsagittal_df = df[df.series_description != 'Axial T2']\nargs = sagittal_df.drop_duplicates(['study_id', 'series_id'])[['study_id', 'series_id']].values\n\nfrom multiprocessing import Pool\n\np = Pool(processes=4)\nresults = []\nwith tqdm(total=len(args)) as pbar:\n    for res in p.imap(read_sagittal_dicom, args):\n        results.append(res)\n        pbar.update(1)\np.close()\n\nsagittal_df = pd.concat(results)\nsagittal_df.to_csv('sagittal_df.csv', index=False)\n\nsagittal_df = pd.read_csv('sagittal_df.csv')\nseries_description_df = pd.read_csv(f'/kaggle/input/rsna-2024-lumbar-spine-degenerative-classification/{train_test}_series_descriptions.csv')\nsagittal_df = sagittal_df.merge(series_description_df, on=['study_id', 'series_id'])\n\nfor study_id_n, (study_id, idf) in enumerate(tqdm(sagittal_df.groupby('study_id'))):\n    t1 = idf[idf.series_description=='Sagittal T1'].sort_values('instance_number')\n    if len(t1)!=0:\n        t1 = t1[t1.series_id==t1.series_id.values[0]]\n    t2 = idf[idf.series_description=='Sagittal T2/STIR'].sort_values('instance_number')\n    if len(t2)!=0:\n        t2 = t2[t2.series_id==t2.series_id.values[0]]\n    if len(t1)==0:\n        m = t2.instance_number.max()\n        mi = t2.instance_number.min()\n    elif (len(t2)==0):\n        m = t1.instance_number.max()\n        mi = t1.instance_number.min()\n    else:\n        m = max([t1.instance_number.max(), t2.instance_number.max()])\n        mi = min([t1.instance_number.min(), t2.instance_number.min()])\n    for n in range(mi, m+1):\n        n1 = t1[t1.instance_number == n]\n        n2 = t2[t2.instance_number == n]\n        if len(n1) != 0:\n            # print(n1.path.values[0])\n            im1 = cv2.imread(n1.path.values[0])[:,:,1]\n        else:\n            im1 = None\n        if len(n2) != 0:\n            # print(n2.path.values[0])\n            im2 = cv2.imread(n2.path.values[0])[:,:,1]\n        else:\n            im2 = None\n        if im1 is None:\n            # raise\n            im1 = np.zeros(im2.shape)\n        if im2 is None:\n            # raise\n            im2 = np.zeros(im1.shape)\n        if im1.shape!=im2.shape:\n            im1 = cv2.resize(im1, (im2.shape[1], im2.shape[0]))\n        im = np.array([im1, im2, im1]).transpose((1,2,0))\n\n        cv2.imwrite(f'/kaggle/temp/sagittal_all_images/{study_id}___{n}.png', im)\n","metadata":{"execution":{"iopub.status.busy":"2025-09-07T09:32:15.257045Z","iopub.execute_input":"2025-09-07T09:32:15.257363Z","iopub.status.idle":"2025-09-07T09:32:18.409692Z","shell.execute_reply.started":"2025-09-07T09:32:15.257318Z","shell.execute_reply":"2025-09-07T09:32:18.408613Z"},"trusted":true},"outputs":[{"name":"stderr","text":"100%|██████████| 1/1 [00:00<00:00,  3.17it/s]\n100%|██████████| 2/2 [00:01<00:00,  1.75it/s]\n100%|██████████| 1/1 [00:01<00:00,  1.47s/it]\n","output_type":"stream"}],"execution_count":7},{"cell_type":"code","source":"axial_closest_df = axial_closest_df[axial_closest_df.closest==1]\naxial_closest_df['pred_level'] = axial_closest_df.level.values\naxial_closest_df['path'] = '/kaggle/temp/axial_all_images/' + axial_closest_df.study_id.astype(str) + '___' + axial_closest_df.series_id.astype(str) + '___' + axial_closest_df.instance_number.astype(str) + '.png'\naxial_closest_df['level_pred'] = 1\naxial_closest_df[['study_id', 'series_id', 'path', 'level_pred', 'pred_level', 'instance_number', 'z']]\naxial_all_level_df = axial_closest_df[['study_id', 'series_id', 'path', 'level_pred', 'pred_level', 'instance_number', 'z', 'dis']]\naxial_all_level_df.to_csv('axial_all_level_df.csv', index=False)","metadata":{"execution":{"iopub.status.busy":"2025-09-07T09:34:38.774549Z","iopub.execute_input":"2025-09-07T09:34:38.774921Z","iopub.status.idle":"2025-09-07T09:34:38.788254Z","shell.execute_reply.started":"2025-09-07T09:34:38.774893Z","shell.execute_reply":"2025-09-07T09:34:38.787352Z"},"trusted":true},"outputs":[],"execution_count":8},{"cell_type":"markdown","source":"## stage 2:","metadata":{}},{"cell_type":"code","source":"import albumentations as A\nfrom albumentations.pytorch import ToTensorV2\nimport timm\n\nclass RSNA2024DatasetV1(Dataset):\n    def __init__(self, cfg):\n        self.transforms = cfg.transforms\n        self.cfg = cfg\n        self.paths = cfg.df.path.values\n        if 'x_min' in list(cfg.df):\n            self.boxes = cfg.df[['x_min', 'y_min', 'x_max', 'y_max']].values\n            \n    def __len__(self):\n        return len(self.paths)\n\n    def __getitem__(self, idx):\n        path = self.paths[idx]\n        image = cv2.imread(path)[:,:,::-1]\n\n        if hasattr(self.cfg, 'box_crop') and self.cfg.box_crop:\n            box = self.boxes[idx]\n            x_pad = (box[2] - box[0])//2 * self.cfg.box_crop_x_ratio\n            y_pad = (box[3] - box[1])//2 * self.cfg.box_crop_y_ratio\n            x_min = np.max([box[0]-x_pad, 0])\n            y_min = np.max([box[1]-y_pad, 0])\n            if hasattr(self.cfg, 'box_crop_y_upper_ratio'):\n                y_upper_pad = (box[3] - box[1])//2 * self.cfg.box_crop_y_upper_ratio\n                y_min = np.max([box[1]-y_upper_pad, 0])\n            x_max = np.min([box[2]+x_pad, image.shape[1]])\n            y_max = np.min([box[3]+y_pad, image.shape[0]])\n            s = image.shape\n            image = image[int(y_min):int(y_max), int(x_min):int(x_max), :]\n            # print(s, image.shape)\n\n        image = self.transforms(image=image)['image']\n\n        return image\n\nclass base():\n    def __init__(self):\n        self.model_name = 'convnext_small.in12k_ft_in1k_384'\n        self.image_size = 384\n        self.batch_size = 16\n        self.tta = False\n        self.box_crop = None\n        self.transforms = A.Compose([\n            A.Resize(self.image_size, self.image_size),\n            A.Normalize(),\n            ToTensorV2(),\n        ])\n        \nclass rsna_sagittal_cl(base):\n    def __init__(self):\n        super().__init__()\n        self.df = pd.read_csv('sagittal_df.csv')\n        self.label_features = ['l1_spinal', 'l2_spinal', 'l3_spinal', 'l4_spinal', 'l5_spinal',\n                               'l1_right_neural', 'l2_right_neural', 'l3_right_neural', 'l4_right_neural', 'l5_right_neural',\n                               'l1_left_neural', 'l2_left_neural', 'l3_left_neural', 'l4_left_neural', 'l5_left_neural']\n        self.image_size = 256\n        \n        d = '/kaggle/input/rsna-2024-sagittal-models-v1/rsna-sagittal-level-cl-spinal-nfn-v2'\n        self.model = timm.create_model(self.model_name, pretrained=False, num_classes=len(self.label_features))\n        if debug:\n            self.model_paths = [f'{d}/last_fold{debug_fold}.ckpt' for fold in range(5)]\n        else:\n            self.model_paths = [f'{d}/last_fold{fold}.ckpt' for fold in range(5)]\n        self.transforms = A.Compose([\n            A.Compose([\n                A.Resize(self.image_size, self.image_size),\n                A.Normalize(),\n                ToTensorV2(),\n            ])\n        ])              \n\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-09-07T09:34:40.364863Z","iopub.execute_input":"2025-09-07T09:34:40.365469Z","iopub.status.idle":"2025-09-07T09:34:40.383484Z","shell.execute_reply.started":"2025-09-07T09:34:40.365441Z","shell.execute_reply":"2025-09-07T09:34:40.382769Z"}},"outputs":[],"execution_count":9},{"cell_type":"code","source":"all_preds = []\nfor cfg in [\n    rsna_sagittal_cl(),\n]:\n    models = []\n    for model_path in cfg.model_paths:\n        # print(model_path)\n        state_dict = torch.load(model_path, map_location=torch.device('cpu'))\n        model = copy.deepcopy(cfg.model)\n        model.load_state_dict(state_dict)\n        model.to(DEVICE)\n        model.eval()\n        models.append(model)\n\n    ds = RSNA2024DatasetV1(cfg)\n    loader = DataLoader(ds, batch_size=cfg.batch_size, shuffle=False, drop_last=False, num_workers=4)\n    preds = []\n    for images in tqdm(loader, smoothing=0):\n        images = images.to(DEVICE)\n        batch_preds = []\n        for model in models:\n            batch_preds.append(model(images).detach().cpu().numpy())\n\n        preds += np.mean(batch_preds, axis=0).tolist()\n    all_preds.append(preds)\nfs = [f'pred_{col}' for col in cfg.label_features]\ncfg.df[fs] = sigmoid(np.mean(all_preds, 0))\ncfg.df.to_csv(f'sagittal_with_position_preds.csv', index=False)\n\n\nsagittal = cfg.df.copy()\nsagittal['pred_spinal'] = sagittal[['pred_l1_spinal', 'pred_l2_spinal', 'pred_l3_spinal', 'pred_l4_spinal', 'pred_l5_spinal']].mean(1)\nsagittal['pred_right_neural'] = sagittal[['pred_l1_right_neural', 'pred_l2_right_neural', 'pred_l3_right_neural', 'pred_l4_right_neural', 'pred_l5_right_neural']].mean(1)\nsagittal['pred_left_neural'] = sagittal[['pred_l1_left_neural', 'pred_l2_left_neural', 'pred_l3_left_neural', 'pred_l4_left_neural', 'pred_l5_left_neural']].mean(1)\n\n\nseries_description_df = pd.read_csv(f'/kaggle/input/rsna-2024-lumbar-spine-degenerative-classification/{train_test}_series_descriptions.csv')\nt2_ids = series_description_df[series_description_df.series_description=='Sagittal T2/STIR'].series_id\n\ndfs = []\nfor id, idf in sagittal[sagittal.series_id.isin(t2_ids)].groupby('series_id'):\n    idf = idf.sort_values(['x_pos', 'instance_number'])\n    idf = idf.drop_duplicates('x_pos')\n    ldf = idf[idf['pred_spinal']==idf['pred_spinal'].max()].iloc[:1]\n    dfs.append(ldf)\n\nspinal_sagittal = pd.concat(dfs)\nsagittal.to_csv('sagittal_df.csv', index=False)\nspinal_sagittal.to_csv('spinal_sagittal_df.csv', index=False)\nspinal_sagittal.study_id.nunique(), spinal_sagittal.series_id.nunique()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-09-07T09:34:42.027008Z","iopub.execute_input":"2025-09-07T09:34:42.027648Z","iopub.status.idle":"2025-09-07T09:34:54.418038Z","shell.execute_reply.started":"2025-09-07T09:34:42.02762Z","shell.execute_reply":"2025-09-07T09:34:54.417091Z"}},"outputs":[{"name":"stderr","text":"100%|██████████| 4/4 [00:03<00:00,  1.26it/s]\n","output_type":"stream"},{"execution_count":10,"output_type":"execute_result","data":{"text/plain":"(1, 1)"},"metadata":{}}],"execution_count":10},{"cell_type":"markdown","source":"### yolox","metadata":{}},{"cell_type":"code","source":"# find left\nbox_cols = ['x_min', 'y_min', 'x_max','y_max']\nyolo_for_config_dir = '/kaggle/input/rsna-2024-axial-models-v1/rsna-axial-all-images-left-yolox-x'\n\nyolo_for_config = 'rsna_axial_all_images_left_yolox_x'\n\nimport sys\nsys.path.append('/kaggle/input/yolox20230421/YOLOX')\n\nfrom yolox.utils import postprocess\nfrom yolox.data.data_augment import ValTransform\n\n\nclass MyDataset(Dataset):\n    def __init__(self, df):\n        self.paths = df.path.values\n        self.preproc = ValTransform(legacy = False)\n        self.size = 512\n\n    def __len__(self):\n        return len(self.paths)\n\n    def __getitem__(self, idx):\n        path = self.paths[idx]\n        img = cv2.imread(path)\n        ratio = min(self.size / img.shape[0], self.size / img.shape[1])\n\n        img, _ = self.preproc(img, None, (self.size, self.size))\n        img = torch.from_numpy(img).float()\n        img = img.float()\n\n        return img, path, ratio\n\nfrom yolox.exp import Exp as MyExp\n\nclass ExpX(MyExp):\n    def __init__(self):\n        super(ExpX, self).__init__()\n        self.depth = 1.33\n        self.width = 1.25\n        self.exp_name = ''\n        self.data_dir = \"\"\n\n        ### need change ###\n        self.max_epoch = 10\n        self.output_dir = \".\"\n        self.input_size = (512, 512)\n        self.test_size = (512, 512)\n        self.no_aug_epochs = 10 # 15\n        self.warmup_epochs = 5 # 5\n        self.num_classes = 1\n        ### need change ###\n\n        ### fyi ###\n        self.data_num_workers = 16\n        self.eval_interval = 1\n        self.seed = 42\n        self.print_interval = 100000\n        self.eval_interval = 1\n        self.save_history_ckpt = False\n        self.mosaic_prob = 1.0\n        self.mixup_prob = 1.0\n        self.hsv_prob = 1.0\n        self.flip_prob = 0.5\n        self.degrees = 10.0\n        self.translate = 0.1\n        self.mosaic_scale = (0.1, 2)\n        self.enable_mixup = True\n        self.mixup_scale = (0.5, 1.5)\n        self.shear = 2.0\n        self.min_lr_ratio = 0.05\n        self.basic_lr_per_img = 0.00015625\n        self.scheduler = 'yoloxwarmcos'\n        self.ema = True\n        self.weight_decay = 0.0005\n        self.momentum = 0.9\n        self.test_conf = 0.01\n        self.nmsthre = 0.65\n        self.class_id_name_map = {0: 'left'}\n\nclass ExpL(MyExp):\n    def __init__(self):\n        super(ExpL, self).__init__()\n        self.depth = 1.0\n        self.width = 1.0\n        self.exp_name = ''\n        self.data_dir = \"\"\n\n        ### need change ###\n        self.max_epoch = 10\n        self.output_dir = \".\"\n        self.input_size = (512, 512)\n        self.test_size = (512, 512)\n        self.no_aug_epochs = 10 # 15\n        self.warmup_epochs = 5 # 5\n        self.num_classes = 1\n        ### need change ###\n\n        ### fyi ###\n        self.data_num_workers = 16\n        self.eval_interval = 1\n        self.seed = 42\n        self.print_interval = 100000\n        self.eval_interval = 1\n        self.save_history_ckpt = False\n        self.mosaic_prob = 1.0\n        self.mixup_prob = 1.0\n        self.hsv_prob = 1.0\n        self.flip_prob = 0.5\n        self.degrees = 10.0\n        self.translate = 0.1\n        self.mosaic_scale = (0.1, 2)\n        self.enable_mixup = True\n        self.mixup_scale = (0.5, 1.5)\n        self.shear = 2.0\n        self.min_lr_ratio = 0.05\n        self.basic_lr_per_img = 0.00015625\n        self.scheduler = 'yoloxwarmcos'\n        self.ema = True\n        self.weight_decay = 0.0005\n        self.momentum = 0.9\n        self.test_conf = 0.01\n        self.nmsthre = 0.65\n        self.class_id_name_map = {0: 'left'}\n\nexp_x = ExpX()\n\n# set inference parameters\nconfthre = 0.03\nnmsthre = 0.45\n\n# get YOLOX model\nmodels = []\nfor fold in range(1):\n    model = exp_x.get_model()\n    model.to(DEVICE)\n    model.eval()\n    model.head.training=False\n    model.training=False\n    ckpt_file = \"/kaggle/input/notebookfa5fb155d5/rsna-axial-all-images-left-yolox-x/0_best_ckpt.ckpt\"\n    # print(ckpt_file)\n    ckpt = torch.load(ckpt_file, map_location=\"cpu\")\n    model.load_state_dict(ckpt[\"model\"])\n    models.append(model)\n\nds = MyDataset(axial_all_level_df)\nloader = DataLoader(ds, batch_size=16, shuffle=False, drop_last=False, num_workers=4)\n\nall_preds_list = [[]]\nall_ratios_list = [[]]\nall_paths_list = [[]]\n\n# print('inf start...')\nwith torch.no_grad():\n    for loader_n, input in tqdm(enumerate(loader)):\n        # if loader_n % 100 == 0:\n        #     print(loader_n)\n        images, paths, ratios = input\n        images = images.to(DEVICE)\n        for model_n, model in enumerate(models):\n            outputs = model(images)\n#             outputs2 = postprocess(\n#                         outputs, exp_x.num_classes, confthre,\n#                         nmsthre, class_agnostic=True\n#                     )\n            \n#             st()\n            \n            outputs = postprocess(\n                        outputs, exp_x.num_classes, confthre,\n                        nmsthre, class_agnostic=True\n                    )\n            all_preds_list[model_n] += outputs\n            all_ratios_list[model_n] += list(ratios)\n            all_paths_list[model_n] += list(paths)\n\ndf_5models = []\nfor model_n, (all_preds, all_paths, all_ratios) in enumerate(zip(all_preds_list, all_paths_list, all_ratios_list)):\n    dfs = []\n    all_boxes = []\n    all_class_ids = []\n    all_scores = []\n    for n, (predictions, path, ratio)  in enumerate(zip(all_preds, all_paths, all_ratios)):\n        if predictions is None:\n            continue\n        predictions = predictions.cpu().numpy()\n\n        bboxes = predictions[:, 0:4]\n\n        bboxes /= ratio\n        bboxes = bboxes.tolist()\n        bbclasses = predictions[:, 6]\n        scores = predictions[:, 4] * predictions[:, 5]\n        path_df = axial_all_level_df[axial_all_level_df.path==path].iloc[:1]\n        for box, score, class_id in zip(bboxes, scores, bbclasses):\n            all_boxes.append(box)\n            all_scores.append(score)\n            all_class_ids.append(class_id)\n            dfs.append(path_df)\n    tmp = pd.concat(dfs)\n    tmp['class_id'] = all_class_ids\n    tmp['class_id'] = tmp['class_id'].astype(int)\n    tmp['class_name'] = tmp['class_id'].map(exp_x.class_id_name_map)\n    tmp['conf'] = all_scores\n    tmp[box_cols] = all_boxes\n    tmp[box_cols] = np.round(tmp[box_cols]).astype(int)\n    tmp['model_n'] = model_n\n    df_5models.append(tmp)\n\nfrom ensemble_boxes import *\nfrom multiprocessing import cpu_count\nimport copy\n\nfrom multiprocessing import Pool\n\ndef exec(args):\n    path, path_df = args\n    boxes_list = []\n    confs_list = []\n    labels_list = []\n    for _, model_df in path_df.groupby('model_n'):\n        boxes_list.append(model_df[box_cols].values/max_value)\n        confs_list.append(model_df['conf'].values.tolist())\n        labels_list.append(model_df['class_id'].values.tolist())\n    boxes, confs, labels = weighted_boxes_fusion(boxes_list, confs_list, labels_list, weights=[1]*len(boxes_list), iou_thr=iou_thr, skip_box_thr=skip_box_thr)\n    boxes *= max_value\n    results = []\n    for idx, box in enumerate(boxes):\n        results.append({\n            \"path\": path,\n            \"class_id\": int(labels[idx]),\n            'conf':confs[idx],\n            \"x_min\": box[0],\n            \"y_min\": box[1],\n            \"x_max\": box[2],\n            \"y_max\": box[3],\n        })\n    return results\n\nresult_oof = copy.deepcopy(df_5models[0])\nyolo_pred_df = pd.concat(df_5models)\nweights = [1]* len(df_5models)\niou_thr = 0.4\nmin_conf = yolo_pred_df.conf.min()\nskip_box_thr = 0.0001\nresults = []\nmax_value = 12800\n\nclass_id_name_map = {}\ntmp = result_oof.drop_duplicates('class_name')\nfor class_id, class_name in zip(tmp.class_id.values, tmp.class_name.values):\n    class_id_name_map[class_id] = class_name\n\nwbf_result_maps_list = []\ndf_list = list(yolo_pred_df.groupby('path'))\np = Pool(processes=cpu_count())\nwith tqdm(total=len(df_list)) as pbar:\n    for wbf_result_maps in p.imap(exec, df_list):\n        wbf_result_maps_list += wbf_result_maps\n        pbar.update(1)\np.close()\n\nresults = pd.DataFrame(wbf_result_maps_list)\nfor col in ['class_name', 'class_id', 'conf', 'x_min', 'y_min', 'x_max', 'y_max']:\n    if col in list(result_oof):\n        del result_oof[col]\nresults = results.merge(result_oof.drop_duplicates('path'), on='path')\nresults['class_name'] = results['class_id'].map(class_id_name_map)\ndel results['model_n']\n\ndfs = []\nfor i, idf in results[['path', 'conf','class_id' ,'x_min','y_min','x_max','y_max']].groupby('path'):\n    dfs.append(idf[idf.conf==idf.conf.max()].iloc[:1])\nresults = pd.concat(dfs)\n\nresults.to_csv(f'yolo_results_axial_left.csv', index=False)\nresults.head()\n\nif plot:\n    for path, pdf in results[results.path.isin(results.path.unique()[:5])].groupby('path'):\n        pdf = pdf[pdf.conf==pdf.conf.max()].iloc[:1]\n        im = cv2.imread(path)\n        im = cv2.cvtColor(im, cv2.COLOR_BGR2RGB)\n        for class_id, box in zip(pdf.class_id.values, pdf[box_cols].values):\n            cv2.rectangle(\n                im,\n                pt1=(int(box[0]), int(box[1])),\n                pt2=(int(box[2]), int(box[3])),\n                color=(255,255,255),\n                thickness=3\n            )\n        plt.imshow(im[:,:,0], 'gray')\n        plt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-09-07T09:34:54.420584Z","iopub.execute_input":"2025-09-07T09:34:54.420968Z","iopub.status.idle":"2025-09-07T09:35:04.226705Z","shell.execute_reply.started":"2025-09-07T09:34:54.420931Z","shell.execute_reply":"2025-09-07T09:35:04.222242Z"}},"outputs":[{"name":"stderr","text":"1it [00:01,  1.00s/it]\n100%|██████████| 5/5 [00:00<00:00, 293.38it/s]\n","output_type":"stream"}],"execution_count":11},{"cell_type":"code","source":"box_cols = ['x_min', 'y_min', 'x_max','y_max']\nyolo_for_config_dir = '/kaggle/input/rsna-2024-axial-models-v1/rsna-axial-all-images-right-yolox-x'\n\nyolo_for_config = 'rsna_axial_all_images_right_yolox_x'\n\nimport sys\nsys.path.append('/kaggle/input/yolox20230421/YOLOX')\n\nfrom yolox.utils import postprocess\nfrom yolox.data.data_augment import ValTransform\n\n\nclass MyDataset(Dataset):\n    def __init__(self, df):\n        self.paths = df.path.values\n        self.preproc = ValTransform(legacy = False)\n        self.size = 512\n\n    def __len__(self):\n        return len(self.paths)\n\n    def __getitem__(self, idx):\n        path = self.paths[idx]\n        img = cv2.imread(path)\n        ratio = min(self.size / img.shape[0], self.size / img.shape[1])\n\n        img, _ = self.preproc(img, None, (self.size, self.size))\n        img = torch.from_numpy(img).float()\n        img = img.float()\n\n        return img, path, ratio\n\nfrom yolox.exp import Exp as MyExp\n\nclass ExpX(MyExp):\n    def __init__(self):\n        super(ExpX, self).__init__()\n        self.depth = 1.33\n        self.width = 1.25\n        self.exp_name = ''\n        self.data_dir = \"\"\n\n        ### need change ###\n        self.max_epoch = 10\n        self.output_dir = \".\"\n        self.input_size = (512, 512)\n        self.test_size = (512, 512)\n        self.no_aug_epochs = 10 # 15\n        self.warmup_epochs = 5 # 5\n        self.num_classes = 1\n        ### need change ###\n\n        ### fyi ###\n        self.data_num_workers = 16\n        self.eval_interval = 1\n        self.seed = 42\n        self.print_interval = 100000\n        self.eval_interval = 1\n        self.save_history_ckpt = False\n        self.mosaic_prob = 1.0\n        self.mixup_prob = 1.0\n        self.hsv_prob = 1.0\n        self.flip_prob = 0.5\n        self.degrees = 10.0\n        self.translate = 0.1\n        self.mosaic_scale = (0.1, 2)\n        self.enable_mixup = True\n        self.mixup_scale = (0.5, 1.5)\n        self.shear = 2.0\n        self.min_lr_ratio = 0.05\n        self.basic_lr_per_img = 0.00015625\n        self.scheduler = 'yoloxwarmcos'\n        self.ema = True\n        self.weight_decay = 0.0005\n        self.momentum = 0.9\n        self.test_conf = 0.01\n        self.nmsthre = 0.65\n        self.class_id_name_map = {0: 'right'}\n\nclass ExpL(MyExp):\n    def __init__(self):\n        super(ExpL, self).__init__()\n        self.depth = 1.0\n        self.width = 1.0\n        self.exp_name = ''\n        self.data_dir = \"\"\n\n        ### need change ###\n        self.max_epoch = 10\n        self.output_dir = \".\"\n        self.input_size = (512, 512)\n        self.test_size = (512, 512)\n        self.no_aug_epochs = 10 # 15\n        self.warmup_epochs = 5 # 5\n        self.num_classes = 1\n        ### need change ###\n\n        ### fyi ###\n        self.data_num_workers = 16\n        self.eval_interval = 1\n        self.seed = 42\n        self.print_interval = 100000\n        self.eval_interval = 1\n        self.save_history_ckpt = False\n        self.mosaic_prob = 1.0\n        self.mixup_prob = 1.0\n        self.hsv_prob = 1.0\n        self.flip_prob = 0.5\n        self.degrees = 10.0\n        self.translate = 0.1\n        self.mosaic_scale = (0.1, 2)\n        self.enable_mixup = True\n        self.mixup_scale = (0.5, 1.5)\n        self.shear = 2.0\n        self.min_lr_ratio = 0.05\n        self.basic_lr_per_img = 0.00015625\n        self.scheduler = 'yoloxwarmcos'\n        self.ema = True\n        self.weight_decay = 0.0005\n        self.momentum = 0.9\n        self.test_conf = 0.01\n        self.nmsthre = 0.65\n        self.class_id_name_map = {0: 'right'}\n\nexp_x = ExpX()\n\n# set inference parameters\nconfthre = 0.03\nnmsthre = 0.45\n\n# get YOLOX model\nmodels = []\nfor fold in range(1):\n    model = exp_x.get_model()\n    model.to(DEVICE)\n    model.eval()\n    model.head.training=False\n    model.training=False\n    ckpt_file = \"/kaggle/input/notebookfa5fb155d5/rsna-axial-all-images-right-yolox-x/0_best_ckpt.ckpt\"\n    # print(ckpt_file)\n    ckpt = torch.load(ckpt_file, map_location=\"cpu\")\n    model.load_state_dict(ckpt[\"model\"])\n    models.append(model)\n\nds = MyDataset(axial_all_level_df)\nloader = DataLoader(ds, batch_size=16, shuffle=False, drop_last=False, num_workers=4)\n\nall_preds_list = [[]]\nall_ratios_list = [[]]\nall_paths_list = [[]]\n\n# print('inf start...')\nwith torch.no_grad():\n    for loader_n, input in tqdm(enumerate(loader)):\n        # if loader_n % 100 == 0:\n        #     print(loader_n)\n        images, paths, ratios = input\n        images = images.to(DEVICE)\n        for model_n, model in enumerate(models):\n            outputs = model(images)\n            outputs = postprocess(\n                        outputs, exp_x.num_classes, confthre,\n                        nmsthre, class_agnostic=True\n                    )\n            all_preds_list[model_n] += outputs\n            all_ratios_list[model_n] += list(ratios)\n            all_paths_list[model_n] += list(paths)\n\ndf_5models = []\nfor model_n, (all_preds, all_paths, all_ratios) in enumerate(zip(all_preds_list, all_paths_list, all_ratios_list)):\n    dfs = []\n    all_boxes = []\n    all_class_ids = []\n    all_scores = []\n    for n, (predictions, path, ratio)  in enumerate(zip(all_preds, all_paths, all_ratios)):\n        if predictions is None:\n            continue\n        predictions = predictions.cpu().numpy()\n\n        bboxes = predictions[:, 0:4]\n\n        bboxes /= ratio\n        bboxes = bboxes.tolist()\n        bbclasses = predictions[:, 6]\n        scores = predictions[:, 4] * predictions[:, 5]\n        path_df = axial_all_level_df[axial_all_level_df.path==path].iloc[:1]\n        for box, score, class_id in zip(bboxes, scores, bbclasses):\n            all_boxes.append(box)\n            all_scores.append(score)\n            all_class_ids.append(class_id)\n            dfs.append(path_df)\n    tmp = pd.concat(dfs)\n    tmp['class_id'] = all_class_ids\n    tmp['class_id'] = tmp['class_id'].astype(int)\n    tmp['class_name'] = tmp['class_id'].map(exp_x.class_id_name_map)\n    tmp['conf'] = all_scores\n    tmp[box_cols] = all_boxes\n    tmp[box_cols] = np.round(tmp[box_cols]).astype(int)\n    tmp['model_n'] = model_n\n    df_5models.append(tmp)\n\n\n\n\nfrom ensemble_boxes import *\nfrom multiprocessing import cpu_count\nimport copy\n\nfrom multiprocessing import Pool\n\ndef exec(args):\n    path, path_df = args\n    boxes_list = []\n    confs_list = []\n    labels_list = []\n    for _, model_df in path_df.groupby('model_n'):\n        boxes_list.append(model_df[box_cols].values/max_value)\n        confs_list.append(model_df['conf'].values.tolist())\n        labels_list.append(model_df['class_id'].values.tolist())\n    boxes, confs, labels = weighted_boxes_fusion(boxes_list, confs_list, labels_list, weights=[1]*len(boxes_list), iou_thr=iou_thr, skip_box_thr=skip_box_thr)\n    boxes *= max_value\n    results = []\n    for idx, box in enumerate(boxes):\n        results.append({\n            \"path\": path,\n            \"class_id\": int(labels[idx]),\n            'conf':confs[idx],\n            \"x_min\": box[0],\n            \"y_min\": box[1],\n            \"x_max\": box[2],\n            \"y_max\": box[3],\n        })\n    return results\n\nresult_oof = copy.deepcopy(df_5models[0])\nyolo_pred_df = pd.concat(df_5models)\nweights = [1]* len(df_5models)\niou_thr = 0.4\nmin_conf = yolo_pred_df.conf.min()\nskip_box_thr = 0.0001\nresults = []\nmax_value = 12800\n\nclass_id_name_map = {}\ntmp = result_oof.drop_duplicates('class_name')\nfor class_id, class_name in zip(tmp.class_id.values, tmp.class_name.values):\n    class_id_name_map[class_id] = class_name\n\nwbf_result_maps_list = []\ndf_list = list(yolo_pred_df.groupby('path'))\np = Pool(processes=cpu_count())\nwith tqdm(total=len(df_list)) as pbar:\n    for wbf_result_maps in p.imap(exec, df_list):\n        wbf_result_maps_list += wbf_result_maps\n        pbar.update(1)\np.close()\n\nresults = pd.DataFrame(wbf_result_maps_list)\nfor col in ['class_name', 'class_id', 'conf', 'x_min', 'y_min', 'x_max', 'y_max']:\n    if col in list(result_oof):\n        del result_oof[col]\nresults = results.merge(result_oof.drop_duplicates('path'), on='path')\nresults['class_name'] = results['class_id'].map(class_id_name_map)\ndel results['model_n']\n\ndfs = []\nfor i, idf in results[['path', 'conf','class_id' ,'x_min','y_min','x_max','y_max']].groupby('path'):\n    dfs.append(idf[idf.conf==idf.conf.max()].iloc[:1])\nresults = pd.concat(dfs)    \n\nresults.to_csv(f'yolo_results_axial_right.csv', index=False)\nresults.head()\nif plot:\n\n    for path, pdf in results[results.path.isin(results.path.unique()[:5])].groupby('path'):\n        pdf = pdf[pdf.conf==pdf.conf.max()].iloc[:1]\n        im = cv2.imread(path)\n        im = cv2.cvtColor(im, cv2.COLOR_BGR2RGB)\n        for class_id, box in zip(pdf.class_id.values, pdf[box_cols].values):\n            cv2.rectangle(\n                im,\n                pt1=(int(box[0]), int(box[1])),\n                pt2=(int(box[2]), int(box[3])),\n                color=(255,255,255),\n                thickness=3\n            )\n        plt.imshow(im[:,:,0], 'gray')\n        plt.show()\n\n\nleft = pd.read_csv('yolo_results_axial_left.csv')\nright = pd.read_csv('yolo_results_axial_right.csv')\nfor c in ['x_min', 'y_min', 'x_max', 'y_max']:\n    right = right.rename(columns={c: 'right_'+c})\n    left = left.rename(columns={c: 'left_'+c})\naxial_box_df = right.merge(left[['path']+['left_x_min', 'left_y_min', 'left_x_max', 'left_y_max']], on='path')\naxial_box_df['x_min'] = axial_box_df[['right_x_min', 'left_x_min']].min(1)\naxial_box_df['y_min'] = axial_box_df[['right_y_min', 'left_y_min']].min(1)\naxial_box_df['x_max'] = axial_box_df[['right_x_max', 'left_x_max']].max(1)\naxial_box_df['y_max'] = axial_box_df[['right_y_max', 'left_y_max']].max(1)\naxial_box_df.path.nunique(), len(axial_all_level_df)\nif 'x_min' not in list(axial_all_level_df):\n    axial_all_level_df = axial_all_level_df.merge(axial_box_df[['x_min', 'y_min', 'x_max', 'y_max', 'path']], on='path')\n\n\nfrom multiprocessing import Pool\n\ndef exec(p):\n    im=cv2.imread(p)\n    return im.shape[:2]\n\np = Pool(processes=4)\nargs = axial_all_level_df.path.values\nresults = p.map(exec, args)    \nimage_size_df = pd.DataFrame(results)\nimage_size_df.columns = ['image_height', 'image_width']\nimage_size_df['path'] = args\nif 'image_height' not in list(axial_all_level_df):\n    axial_all_level_df = axial_all_level_df.merge(image_size_df, on='path')\naxial_all_level_df = axial_all_level_df.sort_values(['series_id', 'pred_level'])    \naxial_all_level_df.to_csv('axial_with_box_df.csv', index=False)    \naxial_all_level_df.head(6)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-09-07T09:35:04.229581Z","iopub.execute_input":"2025-09-07T09:35:04.229999Z","iopub.status.idle":"2025-09-07T09:35:12.210556Z","shell.execute_reply.started":"2025-09-07T09:35:04.229954Z","shell.execute_reply":"2025-09-07T09:35:12.209192Z"}},"outputs":[{"name":"stderr","text":"1it [00:00,  2.07it/s]\n100%|██████████| 5/5 [00:00<00:00, 205.60it/s]\n","output_type":"stream"},{"execution_count":12,"output_type":"execute_result","data":{"text/plain":"   study_id   series_id                                               path  \\\n4  44036939  3481971518  /kaggle/temp/axial_all_images/44036939___34819...   \n3  44036939  3481971518  /kaggle/temp/axial_all_images/44036939___34819...   \n2  44036939  3481971518  /kaggle/temp/axial_all_images/44036939___34819...   \n1  44036939  3481971518  /kaggle/temp/axial_all_images/44036939___34819...   \n0  44036939  3481971518  /kaggle/temp/axial_all_images/44036939___34819...   \n\n   level_pred  pred_level  instance_number   z       dis  x_min  y_min  x_max  \\\n4           1           1               17  30  0.818616  130.0  138.0  172.0   \n3           1           2               23  24  0.454633  119.0  122.0  158.0   \n2           1           3               29  18  0.247615   98.0  103.0  137.0   \n1           1           4               34  13  0.592086   98.0   99.0  135.0   \n0           1           5               39   8  0.332912  106.0  110.0  141.0   \n\n   y_max  image_height  image_width  \n4  157.0           256          256  \n3  141.0           256          256  \n2  122.0           256          256  \n1  118.0           256          256  \n0  129.0           256          256  ","text/html":"<div>\n<style scoped>\n    .dataframe tbody tr th:only-of-type {\n        vertical-align: middle;\n    }\n\n    .dataframe tbody tr th {\n        vertical-align: top;\n    }\n\n    .dataframe thead th {\n        text-align: right;\n    }\n</style>\n<table border=\"1\" class=\"dataframe\">\n  <thead>\n    <tr style=\"text-align: right;\">\n      <th></th>\n      <th>study_id</th>\n      <th>series_id</th>\n      <th>path</th>\n      <th>level_pred</th>\n      <th>pred_level</th>\n      <th>instance_number</th>\n      <th>z</th>\n      <th>dis</th>\n      <th>x_min</th>\n      <th>y_min</th>\n      <th>x_max</th>\n      <th>y_max</th>\n      <th>image_height</th>\n      <th>image_width</th>\n    </tr>\n  </thead>\n  <tbody>\n    <tr>\n      <th>4</th>\n      <td>44036939</td>\n      <td>3481971518</td>\n      <td>/kaggle/temp/axial_all_images/44036939___34819...</td>\n      <td>1</td>\n      <td>1</td>\n      <td>17</td>\n      <td>30</td>\n      <td>0.818616</td>\n      <td>130.0</td>\n      <td>138.0</td>\n      <td>172.0</td>\n      <td>157.0</td>\n      <td>256</td>\n      <td>256</td>\n    </tr>\n    <tr>\n      <th>3</th>\n      <td>44036939</td>\n      <td>3481971518</td>\n      <td>/kaggle/temp/axial_all_images/44036939___34819...</td>\n      <td>1</td>\n      <td>2</td>\n      <td>23</td>\n      <td>24</td>\n      <td>0.454633</td>\n      <td>119.0</td>\n      <td>122.0</td>\n      <td>158.0</td>\n      <td>141.0</td>\n      <td>256</td>\n      <td>256</td>\n    </tr>\n    <tr>\n      <th>2</th>\n      <td>44036939</td>\n      <td>3481971518</td>\n      <td>/kaggle/temp/axial_all_images/44036939___34819...</td>\n      <td>1</td>\n      <td>3</td>\n      <td>29</td>\n      <td>18</td>\n      <td>0.247615</td>\n      <td>98.0</td>\n      <td>103.0</td>\n      <td>137.0</td>\n      <td>122.0</td>\n      <td>256</td>\n      <td>256</td>\n    </tr>\n    <tr>\n      <th>1</th>\n      <td>44036939</td>\n      <td>3481971518</td>\n      <td>/kaggle/temp/axial_all_images/44036939___34819...</td>\n      <td>1</td>\n      <td>4</td>\n      <td>34</td>\n      <td>13</td>\n      <td>0.592086</td>\n      <td>98.0</td>\n      <td>99.0</td>\n      <td>135.0</td>\n      <td>118.0</td>\n      <td>256</td>\n      <td>256</td>\n    </tr>\n    <tr>\n      <th>0</th>\n      <td>44036939</td>\n      <td>3481971518</td>\n      <td>/kaggle/temp/axial_all_images/44036939___34819...</td>\n      <td>1</td>\n      <td>5</td>\n      <td>39</td>\n      <td>8</td>\n      <td>0.332912</td>\n      <td>106.0</td>\n      <td>110.0</td>\n      <td>141.0</td>\n      <td>129.0</td>\n      <td>256</td>\n      <td>256</td>\n    </tr>\n  </tbody>\n</table>\n</div>"},"metadata":{}}],"execution_count":12},{"cell_type":"code","source":"box_cols = ['x_min', 'y_min', 'x_max','y_max']\nyolo_for_config_dir = '/kaggle/input/rsna-10classes-yolox-x'\nyolo_for_config = 'rsna_10classes_yolox_x'\n\nimport sys\nsys.path.append('/kaggle/input/yolox20230421/YOLOX')\n\nfrom yolox.utils import postprocess\nfrom yolox.data.data_augment import ValTransform\n\nclass MyDataset(Dataset):\n    def __init__(self, df):\n        self.paths = df.path.values\n        self.preproc = ValTransform(legacy = False)\n        self.size = 512\n\n    def __len__(self):\n        return len(self.paths)\n\n    def __getitem__(self, idx):\n        path = self.paths[idx]\n        img = cv2.imread(path)\n        ratio = min(self.size / img.shape[0], self.size / img.shape[1])\n\n        img, _ = self.preproc(img, None, (self.size, self.size))\n        img = torch.from_numpy(img).float()\n        img = img.float()\n\n        return img, path, ratio\n\nfrom yolox.exp import Exp as MyExp\n\nclass Exp(MyExp):\n    def __init__(self):\n        super(Exp, self).__init__()\n        self.depth = 1.33\n        self.width = 1.25\n        self.exp_name = ''\n        self.data_dir = \"\"\n\n        ### need change ###\n        self.max_epoch = 10\n        self.output_dir = \".\"\n        self.input_size = (512, 512)\n        self.test_size = (512, 512)\n        self.no_aug_epochs = 10 # 15\n        self.warmup_epochs = 5 # 5\n        self.num_classes = 10\n        ### need change ###\n\n        ### fyi ###\n        self.data_num_workers = 16\n        self.eval_interval = 1\n        self.seed = 42\n        self.print_interval = 100\n        self.eval_interval = 1\n        self.save_history_ckpt = False\n        self.mosaic_prob = 1.0\n        self.mixup_prob = 1.0\n        self.hsv_prob = 1.0\n        self.flip_prob = 0.5\n        self.degrees = 10.0\n        self.translate = 0.1\n        self.mosaic_scale = (0.1, 2)\n        self.enable_mixup = True\n        self.mixup_scale = (0.5, 1.5)\n        self.shear = 2.0\n        self.min_lr_ratio = 0.05\n        self.basic_lr_per_img = 0.00015625\n        self.scheduler = 'yoloxwarmcos'\n        self.ema = True\n        self.weight_decay = 0.0005\n        self.momentum = 0.9\n        self.test_conf = 0.01\n        self.nmsthre = 0.65\n        self.class_id_name_map = {\n             0: 'L1/L2_L',\n             1: 'L1/L2_R',\n             2: 'L2/L3_L',\n             3: 'L2/L3_R',\n             4: 'L3/L4_L',\n             5: 'L3/L4_R',\n             6: 'L4/L5_L',\n             7: 'L4/L5_R',\n             8: 'L5/S1_L',\n             9: 'L5/S1_R'\n        }\n\nexp = Exp()\n\n# set inference parameters\nconfthre = 0.03\nnmsthre = 0.45\n\n# get YOLOX model\nmodels = []\nfor fold in range(5):\n    model = exp.get_model()\n    model.cuda()\n    model.eval()\n    model.head.training=False\n    model.training=False\n    if debug:\n        ckpt_file = f\"/kaggle/input/notebookfa5fb155d5/rsna-10classes-yolox-x/{debug_fold}_best_ckpt.ckpt\"\n    else:\n        ckpt_file = f\"/kaggle/input/notebookfa5fb155d5/rsna-10classes-yolox-x/{fold}_best_ckpt.ckpt\"\n    # print(ckpt_file)\n    ckpt = torch.load(ckpt_file, map_location=\"cpu\")\n    model.load_state_dict(ckpt[\"model\"])\n    models.append(model)\nds = MyDataset(spinal_sagittal)\nloader = DataLoader(ds, batch_size=16, shuffle=False, drop_last=False, num_workers=4)\n\nall_preds_list = [[], [], [], [], []]\nall_ratios_list = [[], [], [], [], []]\nall_paths_list = [[], [], [], [], []]\n\n# print('inf start...')\nwith torch.no_grad():\n    for loader_n, input in tqdm(enumerate(loader)):\n        # if loader_n % 100 == 0:\n        #     print(loader_n)\n        images, paths, ratios = input\n        images = images.cuda()\n        for model_n, model in enumerate(models):\n            outputs = model(images)\n            outputs = postprocess(\n                        outputs, exp.num_classes, confthre,\n                        nmsthre, class_agnostic=True\n                    )\n            all_preds_list[model_n] += outputs\n            all_ratios_list[model_n] += list(ratios)\n            all_paths_list[model_n] += list(paths)\n\ndf_5models = []\nfor model_n, (all_preds, all_paths, all_ratios) in enumerate(zip(all_preds_list, all_paths_list, all_ratios_list)):\n    dfs = []\n    all_boxes = []\n    all_class_ids = []\n    all_scores = []\n    for n, (predictions, path, ratio)  in enumerate(zip(all_preds, all_paths, all_ratios)):\n        if predictions is None:\n            continue\n        predictions = predictions.cpu().numpy()\n\n        bboxes = predictions[:, 0:4]\n\n        bboxes /= ratio\n        bboxes = bboxes.tolist()\n        bbclasses = predictions[:, 6]\n        scores = predictions[:, 4] * predictions[:, 5]\n        path_df = sagittal_df[sagittal_df.path==path]\n        for box, score, class_id in zip(bboxes, scores, bbclasses):\n            all_boxes.append(box)\n            all_scores.append(score)\n            all_class_ids.append(class_id)\n            dfs.append(path_df)\n    tmp = pd.concat(dfs)\n    tmp['class_id'] = all_class_ids\n    tmp['class_id'] = tmp['class_id'].astype(int)\n    tmp['class_name'] = tmp['class_id'].map(exp.class_id_name_map)\n    tmp['conf'] = all_scores\n    tmp[box_cols] = all_boxes\n    tmp[box_cols] = np.round(tmp[box_cols]).astype(int)\n    tmp['model_n'] = model_n\n    df_5models.append(tmp)\n\nfrom ensemble_boxes import *\nfrom multiprocessing import cpu_count\nimport copy\n\nfrom multiprocessing import Pool\n\ndef exec(args):\n    path, path_df = args\n    boxes_list = []\n    confs_list = []\n    labels_list = []\n    for _, model_df in path_df.groupby('model_n'):\n        boxes_list.append(model_df[box_cols].values/max_value)\n        confs_list.append(model_df['conf'].values.tolist())\n        labels_list.append(model_df['class_id'].values.tolist())\n    boxes, confs, labels = weighted_boxes_fusion(boxes_list, confs_list, labels_list, weights=[1]*len(boxes_list), iou_thr=iou_thr, skip_box_thr=skip_box_thr)\n    boxes *= max_value\n    results = []\n    for idx, box in enumerate(boxes):\n        results.append({\n            \"path\": path,\n            \"class_id\": int(labels[idx]),\n            'conf':confs[idx],\n            \"x_min\": box[0],\n            \"y_min\": box[1],\n            \"x_max\": box[2],\n            \"y_max\": box[3],\n        })\n    return results\n\nresult_oof = copy.deepcopy(df_5models[0])\nyolo_pred_df = pd.concat(df_5models)\nweights = [1]* len(df_5models)\niou_thr = 0.4\nmin_conf = yolo_pred_df.conf.min()\nskip_box_thr = 0.0001\nresults = []\nmax_value = 12800\n\nclass_id_name_map = {}\ntmp = result_oof.drop_duplicates('class_name')\nfor class_id, class_name in zip(tmp.class_id.values, tmp.class_name.values):\n    class_id_name_map[class_id] = class_name\n\nwbf_result_maps_list = []\ndf_list = list(yolo_pred_df.groupby('path'))\np = Pool(processes=cpu_count())\nwith tqdm(total=len(df_list)) as pbar:\n    for wbf_result_maps in p.imap(exec, df_list):\n        wbf_result_maps_list += wbf_result_maps\n        pbar.update(1)\np.close()\n\nresults = pd.DataFrame(wbf_result_maps_list)\nfor col in ['class_name', 'class_id', 'conf', 'x_min', 'y_min', 'x_max', 'y_max']:\n    if col in list(result_oof):\n        del result_oof[col]\nresults = results.merge(result_oof.drop_duplicates('path'), on='path')\nresults['class_name'] = results['class_id'].map(class_id_name_map)\ndel results['model_n']\n\ndfs = []\nfor i, idf in results.groupby(['path', 'class_id']):\n    dfs.append(idf[idf.conf==idf.conf.max()].iloc[:1])\nresults = pd.concat(dfs)    \n\n\nresults.to_csv(f'yolo_results_sagittal.csv', index=False)\nresults.head()\nif plot:\n\n    for path, pdf in results[results.path.isin(results.path.unique()[:5])].groupby('path'):\n        im = cv2.imread(path)\n        im = cv2.cvtColor(im, cv2.COLOR_BGR2RGB)\n        for class_id, box in zip(pdf.class_id.values, pdf[box_cols].values):\n            cv2.rectangle(\n                im,\n                pt1=(int(box[0]), int(box[1])),\n                pt2=(int(box[2]), int(box[3])),\n                color=(255,255,255),\n                thickness=3\n            )\n        plt.imshow(im[:,:,0], 'gray')\n        plt.show()\n\ndel models\ntorch.cuda.empty_cache()\n\n\nspinal_sagittal = pd.read_csv('spinal_sagittal_df.csv')\nl = len(spinal_sagittal)\nyolo_results_sagittal = pd.read_csv('yolo_results_sagittal.csv')[['path', 'class_id', 'class_name', 'conf','x_min','y_min','x_max','y_max']]\nyolo_results_sagittal['study_id'] = yolo_results_sagittal.path.apply(lambda x: int(x.split('/')[-1].split('___')[0]))\ndel yolo_results_sagittal['path']\n\nsagittal_box_df = spinal_sagittal.merge(yolo_results_sagittal, on='study_id')\nsagittal_box_df['level'] = sagittal_box_df.class_name.apply(lambda x: x.split('_')[0])\nsagittal_box_df['lr'] = sagittal_box_df.class_name.apply(lambda x: x.split('_')[1])\nsagittal_box_df = sagittal_box_df[['study_id', 'path', 'level', 'lr']+box_cols]\nsagittal_box_df.to_csv('sagittal_box_df.csv', index=False)\n\nsagittal_df = pd.read_csv('sagittal_df.csv')                                                        \n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-09-07T09:35:12.215053Z","iopub.execute_input":"2025-09-07T09:35:12.215462Z","iopub.status.idle":"2025-09-07T09:35:42.229418Z","shell.execute_reply.started":"2025-09-07T09:35:12.215416Z","shell.execute_reply":"2025-09-07T09:35:42.228532Z"}},"outputs":[{"name":"stderr","text":"1it [00:00,  1.93it/s]\n100%|██████████| 1/1 [00:00<00:00, 52.50it/s]\n","output_type":"stream"}],"execution_count":13},{"cell_type":"code","source":"# --- FINAL STEP: CREATE THE CROPPED IMAGE DATASET ---\n\nimport pandas as pd\nimport cv2\nimport os\nimport numpy as np\nfrom tqdm import tqdm\n\nprint(\"Starting the final step: Cropping images to create the dataset...\")\n\n# 1. Load the data with the final bounding box coordinates\ndf_to_crop = pd.read_csv('axial_with_box_df.csv')\nprint(f\"Loaded 'axial_with_box_df.csv'. Found {len(df_to_crop)} images to process.\")\n\n# 2. Create the output directory for your new dataset\noutput_dir = '/kaggle/working/axial_cropped_dataset/'\nos.makedirs(output_dir, exist_ok=True)\nprint(f\"Output directory created at: {output_dir}\")\n\n# 3. Define cropping parameters (with padding, just like the 2nd place solution's models would use)\nCROP_X_RATIO = 1.0\nCROP_Y_RATIO = 2.0\n\n# 4. Loop through each image, crop it, and save it\ncropped_image_paths = []\nfor index, row in tqdm(df_to_crop.iterrows(), total=df_to_crop.shape[0], desc=\"Cropping and Saving Images\"):\n    image_path = row['path']\n    image = cv2.imread(image_path)\n    \n    if image is None:\n        cropped_image_paths.append(None)\n        continue\n    \n    x_min, y_min, x_max, y_max = row['x_min'], row['y_min'], row['x_max'], row['y_max']\n    \n    width = x_max - x_min\n    height = y_max - y_min\n    x_pad = (width / 2) * CROP_X_RATIO\n    y_pad = (height / 2) * CROP_Y_RATIO\n    \n    new_x_min = int(np.maximum(0, x_min - x_pad))\n    new_y_min = int(np.maximum(0, y_min - y_pad))\n    new_x_max = int(np.minimum(image.shape[1], x_max + x_pad))\n    new_y_max = int(np.minimum(image.shape[0], y_max + y_pad))\n    \n    cropped_image = image[new_y_min:new_y_max, new_x_min:new_x_max]\n    \n    base_filename = os.path.basename(image_path)\n    output_path = os.path.join(output_dir, base_filename)\n    cv2.imwrite(output_path, cropped_image)\n    \n    cropped_image_paths.append(output_path)\n\n# 5. Create the final metadata CSV for your new dataset\nprint(\"Finalizing metadata file...\")\ndf_to_crop['cropped_image_path'] = cropped_image_paths\ndf_to_crop.dropna(subset=['cropped_image_path'], inplace=True)\n\nfinal_csv_path = '/kaggle/working/axial_final_dataset_metadata.csv'\ndf_to_crop.to_csv(final_csv_path, index=False)\n\nprint(\"\\n--- WORKFLOW COMPLETE! ---\")\nprint(f\"Successfully created a dataset with {len(df_to_crop)} cropped images.\")\nprint(f\"==> Image Directory: {output_dir}\")\nprint(f\"==> Metadata CSV File: {final_csv_path}\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-09-07T09:35:42.264633Z","iopub.execute_input":"2025-09-07T09:35:42.265103Z","iopub.status.idle":"2025-09-07T09:35:42.29617Z","shell.execute_reply.started":"2025-09-07T09:35:42.265072Z","shell.execute_reply":"2025-09-07T09:35:42.295402Z"}},"outputs":[{"name":"stdout","text":"Starting the final step: Cropping images to create the dataset...\nLoaded 'axial_with_box_df.csv'. Found 5 images to process.\nOutput directory created at: /kaggle/working/axial_cropped_dataset/\n","output_type":"stream"},{"name":"stderr","text":"Cropping and Saving Images: 100%|██████████| 5/5 [00:00<00:00, 390.43it/s]","output_type":"stream"},{"name":"stdout","text":"Finalizing metadata file...\n\n--- WORKFLOW COMPLETE! ---\nSuccessfully created a dataset with 5 cropped images.\n==> Image Directory: /kaggle/working/axial_cropped_dataset/\n==> Metadata CSV File: /kaggle/working/axial_final_dataset_metadata.csv\n","output_type":"stream"},{"name":"stderr","text":"\n","output_type":"stream"}],"execution_count":15},{"cell_type":"code","source":"","metadata":{"trusted":true},"outputs":[],"execution_count":null}]}