{"metadata":{"kernelspec":{"language":"python","display_name":"Python 3","name":"python3"},"language_info":{"pygments_lexer":"ipython3","nbconvert_exporter":"python","version":"3.6.4","file_extension":".py","codemirror_mode":{"name":"ipython","version":3},"name":"python","mimetype":"text/x-python"}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"markdown","source":"Everyone go upvote [David Austin's](https://www.kaggle.com/code/tivfrvqhs5/decode-jpeg2000-dicom-with-dali) notebook that showed us the amazing powers of DALI! This a fork of his notebook. For modeling, I set up my local kernel based on [Theo Viel's](https://www.kaggle.com/code/theoviel/rsna-breast-baseline-faster-inference-with-dali) inference kernel, please go up vote this notebook too. They're both amazing and I learned a lot! \n\nThere's more improvements, I am seeing if we can get rid of the last I/O action after cropping but I did not end up doing it here because it will greatly restrict reuseability.\n\nAbout 3.8h runtime (compared to Theo Viel's non full dali pipeline, but this one includes YoloV5 ROI)","metadata":{}},{"cell_type":"code","source":"!pip install -I --no-index --find-links /kaggle/input/offline-rsna-dali-pipeline-pips python-gdcm pydicom pylibjpeg dicomsdl nvidia-dali-nightly-cuda110","metadata":{"_kg_hide-input":true,"_kg_hide-output":true,"execution":{"iopub.status.busy":"2023-01-29T20:32:18.686132Z","iopub.execute_input":"2023-01-29T20:32:18.686567Z","iopub.status.idle":"2023-01-29T20:32:55.646837Z","shell.execute_reply.started":"2023-01-29T20:32:18.68648Z","shell.execute_reply":"2023-01-29T20:32:55.645631Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"import os\nimport gc\nimport sys\nimport glob\nimport time\n\nimport cupy as cp\nimport numpy as np\nimport pandas as pd\n\nimport cv2\nimport torch\nimport pydicom\nimport dicomsdl\nimport matplotlib\n\nfrom tqdm.notebook import tqdm\nimport matplotlib.pyplot as plt\nfrom builtins import StopIteration\nfrom collections import defaultdict\nfrom joblib import Parallel, delayed\nfrom pydicom.filebase import DicomBytesIO\n\nimport torch.nn as nn\nimport albumentations as albu\nfrom albumentations.pytorch import ToTensorV2\nfrom torch.utils.data import Dataset, DataLoader\n\nimport nvidia.dali.fn as fn\nimport nvidia.dali.ops as ops\nimport nvidia.dali.types as types\nfrom nvidia.dali import pipeline_def\nfrom nvidia.dali.pipeline import Pipeline\nfrom nvidia.dali.types import DALIDataType\nfrom nvidia.dali.plugin.pytorch import DALIGenericIterator\nfrom nvidia.dali.plugin.numba.fn.experimental import numba_function\n\nsys.path.append('/kaggle/input/yolov5-github-repo-tracker/yolov5')\nsys.path.append('/kaggle/input/timm-0-6-9/pytorch-image-models-master')\n\nimport timm\n\nfrom utils.general import non_max_suppression","metadata":{"_kg_hide-input":true,"execution":{"iopub.status.busy":"2023-01-29T20:35:15.026603Z","iopub.execute_input":"2023-01-29T20:35:15.026996Z","iopub.status.idle":"2023-01-29T20:35:15.036393Z","shell.execute_reply.started":"2023-01-29T20:35:15.026958Z","shell.execute_reply":"2023-01-29T20:35:15.035351Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"class Config:\n    # dirs\n    img_dir = '../input/rsna-breast-cancer-detection/test_images'\n    oimg_dir = '/tmp/imgs' # output image dir\n    \n    # df\n    df_path = '/kaggle/input/rsna-breast-cancer-detection/test.csv'\n    \n    # roi pipeline \n    batch_size = 16\n    resize_height_to = 640 # yolov5 image size\n    factor = 2.4 # image downscale factor - saves memory \n    # -> 3.5 * 640 = 2240 (later cropped and resize again, higher the better)\n    out_img_size = 1536 # output image size -> outputs ratio 1024x512\n    stretch = True\n    \n    # dali pipeline args\n    pipeline_args = {\n        'batch_size': 16,\n        'num_threads': 1,\n        'py_num_workers': 2,\n        'exec_async': False,\n        'exec_pipelined': False,\n        # multi-gpu\n        'num_gpus': 2\n        \n    }\n    \n    # yolov5\n    yolov5_model = '/kaggle/input/rsna-yolov5-padded/bestv2.pt'\n    yolov5_path = '/kaggle/input/yolov5-github-repo-tracker/yolov5'\n    \n    models = [\n        {\n            'num_workers': 2,\n            'cnn_batch_size': 4,\n            'model_name': 'tf_efficientnetv2_s',\n            'weights_path': '/kaggle/input/rsna-model-weightsv2/1536-test',\n        }\n        \n    ]\n    \n    threshold = [0.28, 0.28] # site 1 and 2\n    \n    debug = False","metadata":{"execution":{"iopub.status.busy":"2023-01-29T20:33:06.744139Z","iopub.execute_input":"2023-01-29T20:33:06.745119Z","iopub.status.idle":"2023-01-29T20:33:06.754323Z","shell.execute_reply.started":"2023-01-29T20:33:06.745063Z","shell.execute_reply":"2023-01-29T20:33:06.753151Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"def __read_metadata(fn):\n    f = open(fn, 'rb')\n    md = f.read(400)\n    f.close()\n    offset = md.index(b'1.2.840.10008.1.2.4.') + 20\n    return md[offset] == 57","metadata":{"execution":{"iopub.status.busy":"2023-01-29T20:33:06.757344Z","iopub.execute_input":"2023-01-29T20:33:06.758122Z","iopub.status.idle":"2023-01-29T20:33:06.767964Z","shell.execute_reply.started":"2023-01-29T20:33:06.758082Z","shell.execute_reply":"2023-01-29T20:33:06.766735Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"df = pd.read_csv(Config.df_path)\n\nif Config.debug:\n#     df = df.sample(1000).reset_index(drop = True)\n    df = df[:36000]\n\ndf['pis']  = Config.img_dir + '/' + df.patient_id.astype(str) + '/' + df.image_id.astype(str) + '.dcm'\ndf['path'] = Config.oimg_dir + '/' + df.patient_id.astype(str) + '_' + df.image_id.astype(str) + '.png'\n\nm_ids = df.machine_id.unique()\nm_id2isj2k = {}\n \nfor m_id in m_ids:\n    r = df.query(\"machine_id == @m_id\").sample().iloc[0]\n    m_id2isj2k[r.machine_id] = __read_metadata(r.pis)\n\nis_j2k = df.machine_id.apply(lambda m_id: m_id2isj2k[m_id])\ndf_j2k = df.loc[is_j2k]\ndf_jll = df.loc[~is_j2k]\n\nos.makedirs(Config.oimg_dir, exist_ok=True)\n\nprint('j2k:', len(df_j2k), 'jll:', len(df_jll))\n\nmodel = torch.hub.load(\n    Config.yolov5_path, 'custom', path = Config.yolov5_model, source = 'local', force_reload = True\n).to('cuda')","metadata":{"execution":{"iopub.status.busy":"2023-01-29T20:36:06.554388Z","iopub.execute_input":"2023-01-29T20:36:06.554814Z","iopub.status.idle":"2023-01-29T20:36:06.641667Z","shell.execute_reply.started":"2023-01-29T20:36:06.554781Z","shell.execute_reply":"2023-01-29T20:36:06.640571Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"windowing_index = 0\nvoi_func_mapping = {\n    'LINEAR': True,\n    'SIGMOID': False,\n    None: True # default case\n}\n\ndef get_windowing_values(dm):\n    skip_windowing = dm.WindowWidth is None or dm.WindowCenter is None\n    voi_func = dm.VOILUTFunction if hasattr(dm, 'VOILUTFunction') else 'LINEAR'\n\n    if not skip_windowing:\n        center = dm.WindowCenter if isinstance(dm.WindowCenter, (int, float, complex)) else dm.WindowCenter[windowing_index]\n        width = dm.WindowWidth if isinstance(dm.WindowWidth, (int, float, complex)) else dm.WindowWidth[windowing_index]\n\n        bits_stored = dm.BitsStored\n\n        y_min: float\n        y_max: float\n\n        if dm.ModalityLUTSequence:\n            y_min = 0\n            item = dm.ModalityLUTSequence[0]\n            bit_depth = item.LUTDescriptor[2]\n            y_max = 2**bit_depth - 1\n        elif dm.PixelRepresentation == 0:\n            y_min = 0\n            y_max = 2**bits_stored - 1\n        else:\n            y_min = -2**(bits_stored - 1)\n            y_max = 2**(bits_stored - 1) - 1\n\n        slope = dm.RescaleSlope\n        intercept = dm.RescaleIntercept\n\n        if slope is not None and intercept is not None:\n            y_min = y_min * slope + intercept\n            y_max = y_max * slope + intercept\n\n        if voi_func == 'LINEAR':\n            center -= 0.5\n            width -= 1\n    else:\n        center = 0\n        width = 0\n        y_min = 0\n        y_max = 0\n    \n    return skip_windowing, voi_func_mapping[voi_func], center, width, y_min, y_max","metadata":{"execution":{"iopub.status.busy":"2023-01-29T17:50:26.792162Z","iopub.execute_input":"2023-01-29T17:50:26.793043Z","iopub.status.idle":"2023-01-29T17:50:26.806479Z","shell.execute_reply.started":"2023-01-29T17:50:26.79299Z","shell.execute_reply":"2023-01-29T17:50:26.805175Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"class J2KIterator(object):\n    def __init__(self, df, batch_size, shard_id = 0, num_shards = 1):\n        self.df = df\n        self.batch_size = batch_size\n        self.shard_id = shard_id\n        self.num_shards = num_shards\n       \n    @staticmethod\n    def convert_dicom_to_j2k(patient_id, image_id):\n        dm = dicomsdl.open(f'{Config.img_dir}/{patient_id}/{image_id}.dcm')\n        \n        with open(f'{Config.img_dir}/{patient_id}/{image_id}.dcm', 'rb') as fp:\n            raw = DicomBytesIO(fp.read())\n            ds = pydicom.dcmread(raw)\n            \n        offset = ds.PixelData.find(b\"\\x00\\x00\\x00\\x0C\")\n        \n        buffer_img = np.frombuffer(ds.PixelData[offset:], dtype=np.uint8)\n        invert_img = dm.PhotometricInterpretation == 'MONOCHROME1'\n        \n        skip_windowing, is_linear, center, width, y_min, y_max = get_windowing_values(dm)\n\n        return buffer_img, invert_img, skip_windowing, is_linear, center, width, y_min, y_max\n        \n    def __len__(self):\n        batches_needed = int(np.ceil(len(self.df) / self.batch_size))\n        batches_per_shard = int(np.ceil(batches_needed / self.num_shards))\n        shard_batches = [batches_per_shard for _ in range(self.num_shards)]\n        \n        if batches_per_shard * self.num_shards != batches_needed:\n            # less batch or half batch in one shard\n            shard_batches[-1] = batches_needed % batches_per_shard\n        \n        return shard_batches[self.shard_id]\n\n    def __iter__(self):\n        self.i = (self.shard_id * self.batch_size)\n        return self\n\n    def __next__(self):\n        if self.i > len(self.df):\n            raise StopIteration\n            \n        buffer_imgs = []\n        invert_imgs = []\n        \n        # windowing\n        skip_windowing_ = []\n        voi_func_ = []\n        c_ = []\n        w_ = []\n        ymin_ = []\n        ymax_ = []\n        \n        # ids\n        p_ids = []\n        i_ids = []\n        \n        df = self.df.iloc[range(self.i, min(self.i + self.batch_size, len(self.df)))]\n        \n        for p_id, i_id in df[['patient_id', 'image_id']].values:\n            buffer_img, invert_img, skip_windowing, voi_func, c, w, ymin, ymax = \\\n                self.convert_dicom_to_j2k(p_id, i_id)\n            \n            buffer_imgs.append(buffer_img)\n            invert_imgs.append(np.array([invert_img], dtype = np.bool_))\n            \n            skip_windowing_.append(np.array([skip_windowing], dtype = np.bool_))\n            voi_func_.append(np.array([voi_func], dtype = np.bool_))\n            c_.append(np.array([c], dtype = np.float64))\n            w_.append(np.array([w], dtype = np.float64))\n            ymin_.append(np.array([ymin], dtype = np.float64))\n            ymax_.append(np.array([ymax], dtype = np.float64))\n            \n            p_ids.append(np.array([p_id], dtype = np.int64)) \n            i_ids.append(np.array([i_id], dtype = np.int64))\n            \n        self.i += (self.batch_size * self.num_shards)\n            \n        return buffer_imgs, invert_imgs, skip_windowing_, voi_func_, c_, w_, ymin_, ymax_, p_ids, i_ids\n    \nclass JLLIterator(object):\n    def __init__(self, df, batch_size, shard_id = 0, num_shards = 1):\n        self.df = df\n        self.batch_size = batch_size\n        self.shard_id = shard_id\n        self.num_shards = num_shards\n\n    @staticmethod\n    def decompress_jll(dm):\n        info = dm.getPixelDataInfo()\n        img = np.empty((info['Rows'], info['Cols']), dtype = info['dtype'])\n        dm.copyFrameData(0, img)\n        return img\n    \n    def process_img(self, patient_id, image_id):\n        dm = dicomsdl.open(f'{Config.img_dir}/{patient_id}/{image_id}.dcm')\n        \n        img = self.decompress_jll(dm).astype(np.uint16)\n        invert_img = dm.PhotometricInterpretation == 'MONOCHROME1'\n        \n        skip_windowing, is_linear, center, width, y_min, y_max = get_windowing_values(dm)\n        \n        return img, invert_img, skip_windowing, is_linear, center, width, y_min, y_max\n        \n    def __len__(self):\n        batches_needed = int(np.ceil(len(self.df) / self.batch_size))\n        batches_per_shard = int(np.ceil(batches_needed / self.num_shards))\n        shard_batches = [batches_per_shard for _ in range(len(self.num_shards))]\n        \n        if batches_per_shard * self.num_shards != batches_needed:\n            # less batch or half batch in one shard\n            shard_batches[-1] = batches_needed % batches_per_shard\n        \n        return shard_batches[self.shard_id]\n\n    def __iter__(self):\n        self.i = (self.shard_id * self.batch_size)\n        return self\n\n    def __next__(self):\n        if self.i > len(self.df):\n            raise StopIteration\n        \n        imgs = []\n        invert_imgs = []\n        \n        # windowing\n        skip_windowing_ = []\n        voi_func_ = []\n        c_ = []\n        w_ = []\n        ymin_ = []\n        ymax_ = []\n        \n        # ids\n        p_ids = []\n        i_ids = []\n        \n        df = self.df.iloc[range(self.i, min(self.i + self.batch_size, len(self.df)))]\n        \n        for p_id, i_id in df[['patient_id', 'image_id']].values:\n            img, invert_img, skip_windowing, voi_func, c, w, ymin, ymax = \\\n                self.process_img(p_id, i_id)\n            \n            imgs.append(np.expand_dims(img, axis = 2))\n            invert_imgs.append(np.array([invert_img], dtype = np.bool_))\n            \n            skip_windowing_.append(np.array([skip_windowing], dtype = np.bool_))\n            voi_func_.append(np.array([voi_func], dtype = np.bool_))\n            c_.append(np.array([c], dtype = np.float64))\n            w_.append(np.array([w], dtype = np.float64))\n            ymin_.append(np.array([ymin], dtype = np.float64))\n            ymax_.append(np.array([ymax], dtype = np.float64))\n            \n            p_ids.append(np.array([p_id], dtype = np.int64))\n            i_ids.append(np.array([i_id], dtype = np.int64))\n            \n        self.i += (self.batch_size * self.num_shards)\n                    \n        return imgs, invert_imgs, skip_windowing_, voi_func_, c_, w_, ymin_, ymax_, p_ids, i_ids","metadata":{"_kg_hide-input":true,"execution":{"iopub.status.busy":"2023-01-29T17:50:26.808615Z","iopub.execute_input":"2023-01-29T17:50:26.809435Z","iopub.status.idle":"2023-01-29T17:50:26.842892Z","shell.execute_reply.started":"2023-01-29T17:50:26.809393Z","shell.execute_reply":"2023-01-29T17:50:26.841715Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# https://github.com/pydicom/pydicom/blob/54c5e493a299e4853bfb46de2e0ddfdd0d285e92/pydicom/pixel_data_handlers/util.py#L468\ndef apply_windowing(img, skip_windowing, voi_func, c, w, ymin, ymax):    \n    if not skip_windowing:\n        yrange = ymax - ymin\n        \n        if voi_func:\n            below = img <= (c - w / 2)\n            above = img > (c + w / 2)\n            between = cp.logical_and(~below, ~above)\n\n            img[below] = ymin\n            img[above] = ymax\n            if between.any():\n                img[between] = (\n                    ((img[between] - c) / w + 0.5) * yrange + ymin\n                )\n        else:\n            img = yrange / (1 + cp.exp(-4 * (img - c) / w)) + ymin\n    \n    # normalize to 8 bit\n    return (255 * (img - img.min()) / (img.max() - img.min())).astype(cp.uint8)\n    \ndef normalize_img(ing_img):\n    return (255 * (in_img - in_img.min()) / (in_img.max() - in_img.min())).astype(cp.uint8)\n\ndef mux(condition, true_case, false_case):\n    neg_condition = condition ^ True\n    return condition * true_case + neg_condition * false_case\n\ndef common_pipeline(imgs, invert_imgs, skip_windowing_, voi_func_, c_, w_, ymin_, ymax_, p_ids, i_ids):\n    # to lower information loss\n    imgs = fn.resize(imgs, resize_y = int(Config.resize_height_to * Config.factor), interp_type = types.INTERP_NN)\n    \n    # apply_windowing & normalize\n    imgs = fn.cast(imgs, dtype = types.FLOAT64) # weird bug with python_function and unsigned ints\n    imgs = fn.python_function(imgs, skip_windowing_, voi_func_, c_, w_, ymin_, ymax_, \n        device = 'gpu', function = apply_windowing, batch_processing = False\n    )\n\n    # monochrome invert using mux\n    imgs_inverted = 255 - imgs\n    imgs = mux(invert_imgs, imgs_inverted, imgs)\n    imgs = fn.cast(imgs, dtype = types.UINT8)\n    \n    # pad to the right to be square\n    imgs = fn.pad(imgs, axes = (1,), shape = (int(Config.resize_height_to * Config.factor)), )\n    \n    # resize for model\n    imgs_2roi = fn.resize(imgs, resize_y = Config.resize_height_to) / 255\n    \n    return imgs_2roi, imgs, p_ids, i_ids","metadata":{"_kg_hide-input":true,"execution":{"iopub.status.busy":"2023-01-29T17:50:26.844564Z","iopub.execute_input":"2023-01-29T17:50:26.844984Z","iopub.status.idle":"2023-01-29T17:50:26.86008Z","shell.execute_reply.started":"2023-01-29T17:50:26.844949Z","shell.execute_reply":"2023-01-29T17:50:26.858852Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"@pipeline_def\ndef j2k_decode_pipeline(shard_id = 0, num_gpus = 1):\n    external_source = J2KIterator(df_j2k, Config.batch_size, shard_id, num_gpus)\n    \n    imgs, invert_imgs, skip_windowing_, voi_func_, c_, w_, ymin_, ymax_, p_ids, i_ids = fn.external_source(\n        source = external_source, num_outputs = 10, device = \"cpu\", \n        dtype = [types.UINT8, types.BOOL, types.BOOL, types.BOOL, types.FLOAT64, types.FLOAT64, types.FLOAT64,\n                 types.FLOAT64, types.INT64, types.INT64],\n#         name = 'RSNAReader',\n    )\n    \n    imgs = fn.experimental.decoders.image(\n        imgs, device = 'mixed', output_type = types.ANY_DATA, dtype = DALIDataType.UINT16\n    )\n    \n    # imgs now on GPU, move other things to GPU\n    skip_windowing_, voi_func_, c_, w_ = skip_windowing_.gpu(), voi_func_.gpu(), c_.gpu(), w_.gpu()\n    ymin_, ymax_, p_ids, i_ids = ymin_.gpu(), ymax_.gpu(), p_ids.gpu(), i_ids.gpu()\n    \n    return common_pipeline(imgs, invert_imgs, skip_windowing_, voi_func_, c_, w_, ymin_, ymax_, p_ids, i_ids)\n\n@pipeline_def\ndef jll_process_pipeline(shard_id = 0, num_gpus = 1):\n    external_source = JLLIterator(df_jll, Config.batch_size, shard_id, num_gpus)\n    \n    imgs, invert_imgs, skip_windowing_, voi_func_, c_, w_, ymin_, ymax_, p_ids, i_ids = fn.external_source(\n        source = external_source, num_outputs = 10, device = \"gpu\",\n        dtype = [types.UINT16, types.BOOL, types.BOOL, types.BOOL, types.FLOAT64, types.FLOAT64, types.FLOAT64,\n                 types.FLOAT64, types.INT64, types.INT64],\n#         name = 'RSNAReader',\n    )\n    \n    imgs = fn.reinterpret(imgs, layout = \"HWC\")\n    \n    return common_pipeline(imgs, invert_imgs, skip_windowing_, voi_func_, c_, w_, ymin_, ymax_, p_ids, i_ids)","metadata":{"_kg_hide-input":true,"execution":{"iopub.status.busy":"2023-01-29T17:50:26.861707Z","iopub.execute_input":"2023-01-29T17:50:26.862259Z","iopub.status.idle":"2023-01-29T17:50:26.876085Z","shell.execute_reply.started":"2023-01-29T17:50:26.862212Z","shell.execute_reply":"2023-01-29T17:50:26.875148Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"def _process(pipe_l, args, p_len):\n    pipe = pipe_l(**args)\n    dali_iter = DALIGenericIterator(pipe, ['imgs_2roi', 'imgs', 'p_ids', 'i_ids'])\n    \n#     pipe.start_py_workers()\n    \n    for i, data in enumerate(dali_iter):\n        imgs_2roi = data[0]['imgs_2roi']\n        imgs = data[0]['imgs'].cpu().numpy()\n        p_ids = data[0]['p_ids'].cpu().numpy()\n        i_ids = data[0]['i_ids'].cpu().numpy()\n\n        with torch.no_grad():\n            y = model(imgs_2roi.expand(-1, -1, -1, 3).permute(0, 3, 1, 2)).cpu()\n            dets = non_max_suppression(y, conf_thres = 0.1, classes = [0, 1])\n            # dets -> 32 x (x y x y conf cls)\n\n            # keep best \n            dets = np.array([\n                _p(d) for d in dets\n            ])\n\n        cls_ = dets[:, 4].astype(np.bool_)\n        xyxy = dets[:, [0, 1, 2, 3]] / Config.resize_height_to\n        xyxy = np.clip(xyxy, 0, 1)\n\n        for img, (xmin, ymin, xmax, ymax), lr, p_id, i_id in zip(imgs, xyxy, cls_, p_ids, i_ids):\n            h, w, _ = img.shape\n            img = np.squeeze(img)\n\n            xmin = (xmin * w).astype(np.uint16)\n            xmax = (xmax * w).astype(np.uint16)\n            ymin = (ymin * h).astype(np.uint16)\n            ymax = (ymax * h).astype(np.uint16)\n\n            if lr: # flip left-right, should work without yolov5 classification\n                img = np.fliplr(img)\n                xmin = w - xmin\n                xmax = w - xmax\n                xmin, xmax = xmax, xmin\n\n            resulting_crop_h = ymax - ymin\n            resulting_crop_w = xmax - xmin\n\n            if (2 * resulting_crop_w) < resulting_crop_h: # increase width\n                # how much more width do I need to add?\n                needed_width = (resulting_crop_h // 2) - resulting_crop_w\n\n                # can I meet width without going past image?\n                if needed_width + xmax > w:\n                    if not Config.stretch:\n                        # padding/resizing is required (I use padding, in this case)\n                        difference = xmax + needed_width - w\n\n                        img = cv2.copyMakeBorder(img, 0, 0, difference, 0, cv2.BORDER_CONSTANT, value = 0)\n\n                        # no need to offset bbox since origin is top left\n                    else:\n                        needed_width = w - xmax - 1\n\n                # expand bbox by needed width\n                xmax += needed_width\n\n            elif (2 * resulting_crop_w) > resulting_crop_h: # increase height\n                # how much more height do I need to add?\n                needed_height = (resulting_crop_w * 2) - resulting_crop_h\n\n                # can I meet height without going past image?\n                if ymin - needed_height < 0:\n                    if not Config.stretch:\n                        # padding/resizing is required (I use padding in this case)\n                        difference = needed_height - ymin\n\n                        img = cv2.copyMakeBorder(img, difference, 0, 0, 0, cv2.BORDER_CONSTANT, value = 0)\n\n                        # offset bbox values to new origin\n                        ymin += difference\n                        ymax += difference\n                    else:\n                        needed_height = ymin - 1 # reset back\n\n                ymin -= needed_height\n\n            if lr: # flip left-right, should work without yolov5 classification\n                img = np.fliplr(img)\n                h, w = img.shape\n\n                xmin = w - xmin\n                xmax = w - xmax\n                xmin, xmax = xmax, xmin\n\n            img = img[ymin:ymax, xmin:xmax]\n\n            h, w = img.shape\n\n            img = cv2.resize(\n                img, dsize = (Config.out_img_size // 2, Config.out_img_size), interpolation=cv2.INTER_NEAREST\n            )\n            \n            cv2.imwrite(f'{Config.oimg_dir}/{p_id[0]}_{i_id[0]}.png', img)\n            \n    del img, imgs, xyxy, cls_, p_ids, i_ids, y, dets, data, dali_iter, pipe\n    gc.collect()\n    torch.cuda.empty_cache()","metadata":{"execution":{"iopub.status.busy":"2023-01-29T17:50:26.87777Z","iopub.execute_input":"2023-01-29T17:50:26.878068Z","iopub.status.idle":"2023-01-29T17:50:26.900153Z","shell.execute_reply.started":"2023-01-29T17:50:26.878042Z","shell.execute_reply":"2023-01-29T17:50:26.899085Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"pipes = [\n    (jll_process_pipeline, len(df_jll)),\n    (j2k_decode_pipeline, len(df_j2k)),\n]","metadata":{"execution":{"iopub.status.busy":"2023-01-29T17:50:26.904965Z","iopub.execute_input":"2023-01-29T17:50:26.905745Z","iopub.status.idle":"2023-01-29T17:50:26.913681Z","shell.execute_reply.started":"2023-01-29T17:50:26.905717Z","shell.execute_reply":"2023-01-29T17:50:26.912554Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"def _p(d):\n    if len(d) > 0 and len(d[0]) >= 1:\n        return d[0].numpy()\n    return np.array([0, 0, Config.resize_height_to, Config.resize_height_to, 1, 0])\n\nfor pipe_l, p_len in pipes:\n    p_name = pipe_l.__name__\n    print(\"Starting pipeline:\", p_name, p_len)\n    \n    if p_len == 0:\n        continue\n        \n    p_time = time.time()\n    \n    # split pipelines between gpus    \n    pipes_args = [{'device_id': i, 'shard_id': i, **Config.pipeline_args}\n                  for i in range(Config.pipeline_args['num_gpus'])]\n    \n    if p_len > Config.pipeline_args['batch_size']:\n        _ = Parallel(n_jobs=2)(delayed(_process)(pipe_l, args, p_len)\n            for args in pipes_args\n        )\n    else:\n        _process(pipe_l, pipes_args[0], p_len)\n    \n    del pipe_l\n    gc.collect()\n    torch.cuda.empty_cache()\n    \n    p_time_elapsed = time.time() - p_time\n    \n    print(f\"Clearing pipeline: took {p_time_elapsed:.0f} seconds\")\ndel pipes, model\ngc.collect()","metadata":{"execution":{"iopub.status.busy":"2023-01-29T17:51:09.24448Z","iopub.execute_input":"2023-01-29T17:51:09.245116Z","iopub.status.idle":"2023-01-29T17:51:09.742522Z","shell.execute_reply.started":"2023-01-29T17:51:09.245057Z","shell.execute_reply":"2023-01-29T17:51:09.741526Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"transforms = albu.Compose(\n    [\n        albu.Normalize(mean=0, std=1),\n        ToTensorV2(),\n    ],\n    p=1,\n)\n\nclass BreastDataset(Dataset):\n    def __init__(self,df, transforms=transforms):\n        self.paths = df['path'].values\n        self.transforms = transforms\n\n    def __len__(self):\n        return len(self.paths)\n\n    def __getitem__(self, idx):\n        image = cv2.imread(self.paths[idx], cv2.IMREAD_GRAYSCALE) # 1 channel\n        \n        if self.transforms:\n            image = self.transforms(image=image)[\"image\"]\n        \n        return image\n    \nclass SimpleCNN(nn.Module):\n    def __init__(self, encoder):\n        super().__init__()\n        \n        self.encoder = encoder\n        \n        self.output_features = encoder.num_features\n        \n        self.logits = nn.Linear(self.output_features, 1) # cancer or no cancer\n        \n        self._to_grayscale()\n        \n    def _to_grayscale(self):\n        for n, m in self.encoder.named_modules():\n            if n:\n                old_conv = getattr(self.encoder, n)\n                old_conv_weight = old_conv.weight.data.sum(1, keepdim=True)\n                new_conv = nn.Conv2d(\n                    1,\n                    old_conv.out_channels,\n                    kernel_size=old_conv.kernel_size,\n                    stride=old_conv.stride,\n                    padding=old_conv.padding,\n                    bias=old_conv.bias is not None,\n                )\n                new_conv.weight.data = old_conv_weight.repeat(1, 1, 1, 1)\n                setattr(self.encoder, n, new_conv)\n                return\n            \n    def extract_features(self, x):\n        fts = self.encoder.forward_features(x)\n        \n        while len(fts.size()) > 2:\n            fts = fts.mean(-1)\n            \n        return fts\n\n    def get_logits(self, features):\n        return self.logits(features)\n    \n    def forward(self, x):\n        x = self.extract_features(x)\n        x = self.get_logits(x)\n        \n        return x\n    \ndef get_model(name):\n    backbone = getattr(timm.models, name)(pretrained = False)\n    backbone.name = name\n    \n    model = SimpleCNN(backbone)\n    \n    return model\n\ndef predict(model, dataloader):\n    model.eval()\n\n    preds = []\n    \n    for img in tqdm(dataloader):\n        img = img.cuda()\n\n        with torch.no_grad():\n            pred = model(img)\n\n        preds.append(pred.sigmoid().cpu().numpy())\n\n    return np.concatenate(preds)","metadata":{"execution":{"iopub.status.busy":"2023-01-29T17:51:12.38577Z","iopub.execute_input":"2023-01-29T17:51:12.386447Z","iopub.status.idle":"2023-01-29T17:51:12.408682Z","shell.execute_reply.started":"2023-01-29T17:51:12.386399Z","shell.execute_reply":"2023-01-29T17:51:12.407387Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"preds = []\n\nfor m_config in Config.models:\n    model = get_model(m_config['model_name']).cuda()\n    dataset = BreastDataset(df)\n    dataloader = DataLoader(dataset, batch_size = m_config['cnn_batch_size'], shuffle = False,\n                            num_workers = m_config['num_workers'])\n\n    for model_weights in glob.glob(f\"{m_config['weights_path']}/*.pth\"):\n        state_dict = torch.load(model_weights, map_location=\"cpu\")\n        model.load_state_dict(state_dict)\n\n        pred = predict(model, dataloader)\n        preds.append(pred)\n\npreds = np.mean(preds, 0)","metadata":{"execution":{"iopub.status.busy":"2023-01-29T17:51:13.384293Z","iopub.execute_input":"2023-01-29T17:51:13.384763Z","iopub.status.idle":"2023-01-29T17:51:20.8417Z","shell.execute_reply.started":"2023-01-29T17:51:13.384722Z","shell.execute_reply":"2023-01-29T17:51:20.840252Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"df['cancer'] = preds\ndf['prediction_id'] = df['patient_id'].astype(str) + '_' + df['laterality']\n\nsub = df[['prediction_id', 'site_id', 'cancer']].groupby(\"prediction_id\").mean().reset_index()\nsub['site_id'] = sub['site_id'].astype(int)\n\nif isinstance(Config.threshold, list):\n    sub['cancer'] = sub.apply(lambda r: r.cancer > Config.threshold[r.site_id - 1], axis=1).astype(int)\nelse:\n    sub[\"cancer\"] = (sub[\"cancer\"] > Config.threshold).astype(int)\n    \nsub = sub[['prediction_id', 'cancer']]\n\nsub.to_csv('/kaggle/working/submission.csv', index=False)\n\nsub.head()","metadata":{"execution":{"iopub.status.busy":"2023-01-29T17:51:20.847297Z","iopub.execute_input":"2023-01-29T17:51:20.849729Z","iopub.status.idle":"2023-01-29T17:51:20.898597Z","shell.execute_reply.started":"2023-01-29T17:51:20.849684Z","shell.execute_reply":"2023-01-29T17:51:20.8976Z"},"trusted":true},"execution_count":null,"outputs":[]}]}