{"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":"# Install packages","metadata":{}},{"cell_type":"code","source":"!pip install /kaggle/input/rsna-2022-whl/{pydicom-2.3.0-py3-none-any.whl,pylibjpeg-1.4.0-py3-none-any.whl,python_gdcm-3.0.15-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl}\n!pip install -q /kaggle/input/nvidia-dali-nightly-cuda110-1230dev/nvidia_dali_nightly_cuda110-1.23.0.dev20230203-7187866-py3-none-manylinux2014_x86_64.whl\n# !pip install /kaggle/input/nvidia-dali-wheel/nvidia_dali_nightly_cuda110-1.22.0.dev20221213-6757685-py3-none-manylinux2014_x86_64.whl\n!pip install /kaggle/input/nvidia-dali-wheel/dicomsdl-0.109.1-cp37-cp37m-manylinux_2_12_x86_64.manylinux2010_x86_64.whl","metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","_kg_hide-output":true,"_kg_hide-input":true,"execution":{"iopub.status.busy":"2023-02-27T01:01:59.421267Z","iopub.execute_input":"2023-02-27T01:01:59.421903Z","iopub.status.idle":"2023-02-27T01:03:44.00151Z","shell.execute_reply.started":"2023-02-27T01:01:59.421798Z","shell.execute_reply":"2023-02-27T01:03:44.000323Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"%%writefile /opt/conda/lib/python3.7/site-packages/nvidia/dali/plugin/pytorch.py\n\n# Copyright (c) 2017-2022, NVIDIA CORPORATION & AFFILIATES. All rights reserved.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\nfrom nvidia.dali.backend import TensorGPU, TensorListGPU\nfrom nvidia.dali.pipeline import Pipeline\nimport nvidia.dali.ops as ops\nfrom nvidia.dali import types\nfrom nvidia.dali.plugin.base_iterator import _DaliBaseIterator\nfrom nvidia.dali.plugin.base_iterator import LastBatchPolicy\nimport torch\nimport torch.utils.dlpack as torch_dlpack\nimport ctypes\nimport numpy as np\n\nto_torch_type = {\n    types.DALIDataType.FLOAT:   torch.float32,\n    types.DALIDataType.FLOAT64: torch.float64,\n    types.DALIDataType.FLOAT16: torch.float16,\n    types.DALIDataType.UINT8:   torch.uint8,\n    types.DALIDataType.INT8:    torch.int8,\n    types.DALIDataType.UINT16:  torch.int16,\n    types.DALIDataType.INT16:   torch.int16,\n    types.DALIDataType.INT32:   torch.int32,\n    types.DALIDataType.INT64:   torch.int64\n}\n\n\ndef feed_ndarray(dali_tensor, arr, cuda_stream=None):\n    \"\"\"\n    Copy contents of DALI tensor to PyTorch's Tensor.\n\n    Parameters\n    ----------\n    `dali_tensor` : nvidia.dali.backend.TensorCPU or nvidia.dali.backend.TensorGPU\n                    Tensor from which to copy\n    `arr` : torch.Tensor\n            Destination of the copy\n    `cuda_stream` : torch.cuda.Stream, cudaStream_t or any value that can be cast to cudaStream_t.\n                    CUDA stream to be used for the copy\n                    (if not provided, an internal user stream will be selected)\n                    In most cases, using pytorch's current stream is expected (for example,\n                    if we are copying to a tensor allocated with torch.zeros(...))\n    \"\"\"\n    dali_type = to_torch_type[dali_tensor.dtype]\n\n    assert dali_type == arr.dtype, (\"The element type of DALI Tensor/TensorList\"\n                                    \" doesn't match the element type of the target PyTorch Tensor: \"\n                                    \"{} vs {}\".format(dali_type, arr.dtype))\n    assert dali_tensor.shape() == list(arr.size()), \\\n        (\"Shapes do not match: DALI tensor has size {0}, but PyTorch Tensor has size {1}\".\n            format(dali_tensor.shape(), list(arr.size())))\n    cuda_stream = types._raw_cuda_stream(cuda_stream)\n\n    # turn raw int to a c void pointer\n    c_type_pointer = ctypes.c_void_p(arr.data_ptr())\n    if isinstance(dali_tensor, (TensorGPU, TensorListGPU)):\n        stream = None if cuda_stream is None else ctypes.c_void_p(cuda_stream)\n        dali_tensor.copy_to_external(c_type_pointer, stream, non_blocking=True)\n    else:\n        dali_tensor.copy_to_external(c_type_pointer)\n    return arr\n\n\nclass DALIGenericIterator(_DaliBaseIterator):\n    \"\"\"\n    General DALI iterator for PyTorch. It can return any number of\n    outputs from the DALI pipeline in the form of PyTorch's Tensors.\n\n    Parameters\n    ----------\n    pipelines : list of nvidia.dali.Pipeline\n                List of pipelines to use\n    output_map : list of str\n                List of strings which maps consecutive outputs\n                of DALI pipelines to user specified name.\n                Outputs will be returned from iterator as dictionary\n                of those names.\n                Each name should be distinct\n    size : int, default = -1\n                Number of samples in the shard for the wrapped pipeline (if there is more than\n                one it is a sum)\n                Providing -1 means that the iterator will work until StopIteration is raised\n                from the inside of iter_setup(). The options `last_batch_policy` and\n                `last_batch_padded` don't work in such case. It works with only one pipeline inside\n                the iterator.\n                Mutually exclusive with `reader_name` argument\n    reader_name : str, default = None\n                Name of the reader which will be queried to the shard size, number of shards and\n                all other properties necessary to count properly the number of relevant and padded\n                samples that iterator needs to deal with. It automatically sets `last_batch_policy`\n                to PARTIAL when the FILL is used, and `last_batch_padded` accordingly to match\n                the reader's configuration\n    auto_reset : string or bool, optional, default = False\n                Whether the iterator resets itself for the next epoch or it requires reset() to be\n                called explicitly.\n\n                It can be one of the following values:\n\n                * ``\"no\"``, ``False`` or ``None`` - at the end of epoch StopIteration is raised\n                  and reset() needs to be called\n                * ``\"yes\"`` or ``\"True\"``- at the end of epoch StopIteration is raised but reset()\n                  is called internally automatically\n\n    dynamic_shape : any, optional,\n                Parameter used only for backward compatibility.\n    fill_last_batch : bool, optional, default = None\n                **Deprecated** Please use ``last_batch_policy`` instead\n\n                Whether to fill the last batch with data up to 'self.batch_size'.\n                The iterator would return the first integer multiple\n                of self._num_gpus * self.batch_size entries which exceeds 'size'.\n                Setting this flag to False will cause the iterator to return\n                exactly 'size' entries.\n    last_batch_policy: optional, default = LastBatchPolicy.FILL\n                What to do with the last batch when there are not enough samples in the epoch\n                to fully fill it. See :meth:`nvidia.dali.plugin.base_iterator.LastBatchPolicy`\n    last_batch_padded : bool, optional, default = False\n                Whether the last batch provided by DALI is padded with the last sample\n                or it just wraps up. In the conjunction with ``last_batch_policy`` it tells\n                if the iterator returning last batch with data only partially filled with\n                data from the current epoch is dropping padding samples or samples from\n                the next epoch. If set to ``False`` next\n                epoch will end sooner as data from it was consumed but dropped. If set to\n                True next epoch would be the same length as the first one. For this to happen,\n                the option `pad_last_batch` in the reader needs to be set to True as well.\n                It is overwritten when `reader_name` argument is provided\n    prepare_first_batch : bool, optional, default = True\n                Whether DALI should buffer the first batch right after the creation of the iterator,\n                so one batch is already prepared when the iterator is prompted for the data\n\n    Example\n    -------\n    With the data set ``[1,2,3,4,5,6,7]`` and the batch size 2:\n\n    last_batch_policy = LastBatchPolicy.PARTIAL, last_batch_padded = True  -> last batch = ``[7]``,\n    next iteration will return ``[1, 2]``\n\n    last_batch_policy = LastBatchPolicy.PARTIAL, last_batch_padded = False -> last batch = ``[7]``,\n    next iteration will return ``[2, 3]``\n\n    last_batch_policy = LastBatchPolicy.FILL, last_batch_padded = True   -> last batch = ``[7, 7]``,\n    next iteration will return ``[1, 2]``\n\n    last_batch_policy = LastBatchPolicy.FILL, last_batch_padded = False  -> last batch = ``[7, 1]``,\n    next iteration will return ``[2, 3]``\n\n    last_batch_policy = LastBatchPolicy.DROP, last_batch_padded = True   -> last batch = ``[5, 6]``,\n    next iteration will return ``[1, 2]``\n\n    last_batch_policy = LastBatchPolicy.DROP, last_batch_padded = False  -> last batch = ``[5, 6]``,\n    next iteration will return ``[2, 3]``\n    \"\"\"\n\n    def __init__(self,\n                 pipelines,\n                 output_map,\n                 size=-1,\n                 reader_name=None,\n                 auto_reset=False,\n                 fill_last_batch=None,\n                 dynamic_shape=False,\n                 last_batch_padded=False,\n                 last_batch_policy=LastBatchPolicy.FILL,\n                 prepare_first_batch=True):\n\n        # check the assert first as _DaliBaseIterator would run the prefetch\n        assert len(set(output_map)) == len(output_map), \"output_map names should be distinct\"\n        self._output_categories = set(output_map)\n        self.output_map = output_map\n\n        _DaliBaseIterator.__init__(self,\n                                   pipelines,\n                                   size,\n                                   reader_name,\n                                   auto_reset,\n                                   fill_last_batch,\n                                   last_batch_padded,\n                                   last_batch_policy,\n                                   prepare_first_batch=prepare_first_batch)\n\n        self._first_batch = None\n        if self._prepare_first_batch:\n            try:\n                self._first_batch = DALIGenericIterator.__next__(self)\n                # call to `next` sets _ever_consumed to True but if we are just calling it from\n                # here we should set if to False again\n                self._ever_consumed = False\n            except StopIteration:\n                assert False, \"It seems that there is no data in the pipeline. This may happen \" \\\n                       \"if `last_batch_policy` is set to PARTIAL and the requested batch size is \" \\\n                       \"greater than the shard size.\"\n\n    def __next__(self):\n        self._ever_consumed = True\n        if self._first_batch is not None:\n            batch = self._first_batch\n            self._first_batch = None\n            return batch\n\n        # Gather outputs\n        outputs = self._get_outputs()\n\n        data_batches = [None for i in range(self._num_gpus)]\n        for i in range(self._num_gpus):\n            dev_id = self._pipes[i].device_id\n            # initialize dict for all output categories\n            category_outputs = dict()\n            # segregate outputs into categories\n            for j, out in enumerate(outputs[i]):\n                category_outputs[self.output_map[j]] = out\n\n            # Change DALI TensorLists into Tensors\n            category_tensors = dict()\n            category_shapes = dict()\n            for category, out in category_outputs.items():\n                category_tensors[category] = out.as_tensor()\n                category_shapes[category] = category_tensors[category].shape()\n\n            category_torch_type = dict()\n            category_device = dict()\n            torch_gpu_device = None\n            torch_cpu_device = torch.device('cpu')\n            # check category and device\n            for category in self._output_categories:\n                category_torch_type[category] = to_torch_type[category_tensors[category].dtype]\n                if type(category_tensors[category]) is TensorGPU:\n                    if not torch_gpu_device:\n                        torch_gpu_device = torch.device('cuda', dev_id)\n                    category_device[category] = torch_gpu_device\n                else:\n                    category_device[category] = torch_cpu_device\n\n            pyt_tensors = dict()\n            for category in self._output_categories:\n                pyt_tensors[category] = torch.empty(category_shapes[category],\n                                                    dtype=category_torch_type[category],\n                                                    device=category_device[category])\n\n            data_batches[i] = pyt_tensors\n\n            # Copy data from DALI Tensors to torch tensors\n            for category, tensor in category_tensors.items():\n                if isinstance(tensor, (TensorGPU, TensorListGPU)):\n                    # Using same cuda_stream used by torch.zeros to set the memory\n                    stream = torch.cuda.current_stream(device=pyt_tensors[category].device)\n                    feed_ndarray(tensor, pyt_tensors[category], cuda_stream=stream)\n                else:\n                    feed_ndarray(tensor, pyt_tensors[category])\n\n        self._schedule_runs()\n\n        self._advance_and_check_drop_last()\n\n        if self._reader_name:\n            if_drop, left = self._remove_padded()\n            if np.any(if_drop):\n                output = []\n                for batch, to_copy in zip(data_batches, left):\n                    batch = batch.copy()\n                    for category in self._output_categories:\n                        batch[category] = batch[category][0:to_copy]\n                    output.append(batch)\n                return output\n\n        else:\n            if self._last_batch_policy == LastBatchPolicy.PARTIAL and (\n                                          self._counter > self._size) and self._size > 0:\n                # First calculate how much data is required to return exactly self._size entries.\n                diff = self._num_gpus * self.batch_size - (self._counter - self._size)\n                # Figure out how many GPUs to grab from.\n                numGPUs_tograb = int(np.ceil(diff / self.batch_size))\n                # Figure out how many results to grab from the last GPU\n                # (as a fractional GPU batch may be required to bring us\n                # right up to self._size).\n                mod_diff = diff % self.batch_size\n                data_fromlastGPU = mod_diff if mod_diff else self.batch_size\n\n                # Grab the relevant data.\n                # 1) Grab everything from the relevant GPUs.\n                # 2) Grab the right data from the last GPU.\n                # 3) Append data together correctly and return.\n                output = data_batches[0:numGPUs_tograb]\n                output[-1] = output[-1].copy()\n                for category in self._output_categories:\n                    output[-1][category] = output[-1][category][0:data_fromlastGPU]\n                return output\n\n        return data_batches\n\n\nclass DALIClassificationIterator(DALIGenericIterator):\n    \"\"\"\n    DALI iterator for classification tasks for PyTorch. It returns 2 outputs\n    (data and label) in the form of PyTorch's Tensor.\n\n    Calling\n\n    .. code-block:: python\n\n       DALIClassificationIterator(pipelines, reader_name)\n\n    is equivalent to calling\n\n    .. code-block:: python\n\n       DALIGenericIterator(pipelines, [\"data\", \"label\"], reader_name)\n\n    Parameters\n    ----------\n    pipelines : list of nvidia.dali.Pipeline\n                List of pipelines to use\n    size : int, default = -1\n                Number of samples in the shard for the wrapped pipeline (if there is more than\n                one it is a sum)\n                Providing -1 means that the iterator will work until StopIteration is raised\n                from the inside of iter_setup(). The options `last_batch_policy` and\n                `last_batch_padded` don't work in such case. It works with only one pipeline inside\n                the iterator.\n                Mutually exclusive with `reader_name` argument\n    reader_name : str, default = None\n                Name of the reader which will be queried to the shard size, number of shards and\n                all other properties necessary to count properly the number of relevant and padded\n                samples that iterator needs to deal with. It automatically sets `last_batch_policy`\n                to PARTIAL when the FILL is used, and `last_batch_padded` accordingly to match\n                the reader's configuration\n    auto_reset : string or bool, optional, default = False\n                Whether the iterator resets itself for the next epoch or it requires reset() to be\n                called explicitly.\n\n                It can be one of the following values:\n\n                * ``\"no\"``, ``False`` or ``None`` - at the end of epoch StopIteration is raised\n                  and reset() needs to be called\n                * ``\"yes\"`` or ``\"True\"``- at the end of epoch StopIteration is raised but reset()\n                  is called internally automatically\n\n    dynamic_shape : any, optional,\n                Parameter used only for backward compatibility.\n    fill_last_batch : bool, optional, default = None\n                **Deprecated** Please use ``last_batch_policy`` instead\n\n                Whether to fill the last batch with data up to 'self.batch_size'.\n                The iterator would return the first integer multiple\n                of self._num_gpus * self.batch_size entries which exceeds 'size'.\n                Setting this flag to False will cause the iterator to return\n                exactly 'size' entries.\n    last_batch_policy: optional, default = LastBatchPolicy.FILL\n                What to do with the last batch when there are not enough samples in the epoch\n                to fully fill it. See :meth:`nvidia.dali.plugin.base_iterator.LastBatchPolicy`\n    last_batch_padded : bool, optional, default = False\n                Whether the last batch provided by DALI is padded with the last sample\n                or it just wraps up. In the conjunction with ``last_batch_policy`` it tells\n                if the iterator returning last batch with data only partially filled with\n                data from the current epoch is dropping padding samples or samples from\n                the next epoch. If set to ``False`` next\n                epoch will end sooner as data from it was consumed but dropped. If set to\n                True next epoch would be the same length as the first one. For this to happen,\n                the option `pad_last_batch` in the reader needs to be set to True as well.\n                It is overwritten when `reader_name` argument is provided\n    prepare_first_batch : bool, optional, default = True\n                Whether DALI should buffer the first batch right after the creation of the iterator,\n                so one batch is already prepared when the iterator is prompted for the data\n\n    Example\n    -------\n    With the data set ``[1,2,3,4,5,6,7]`` and the batch size 2:\n\n    last_batch_policy = LastBatchPolicy.PARTIAL, last_batch_padded = True  -> last batch = ``[7]``,\n    next iteration will return ``[1, 2]``\n\n    last_batch_policy = LastBatchPolicy.PARTIAL, last_batch_padded = False -> last batch = ``[7]``,\n    next iteration will return ``[2, 3]``\n\n    last_batch_policy = LastBatchPolicy.FILL, last_batch_padded = True   -> last batch = ``[7, 7]``,\n    next iteration will return ``[1, 2]``\n\n    last_batch_policy = LastBatchPolicy.FILL, last_batch_padded = False  -> last batch = ``[7, 1]``,\n    next iteration will return ``[2, 3]``\n\n    last_batch_policy = LastBatchPolicy.DROP, last_batch_padded = True   -> last batch = ``[5, 6]``,\n    next iteration will return ``[1, 2]``\n\n    last_batch_policy = LastBatchPolicy.DROP, last_batch_padded = False  -> last batch = ``[5, 6]``,\n    next iteration will return ``[2, 3]``\n    \"\"\"\n\n    def __init__(self,\n                 pipelines,\n                 size=-1,\n                 reader_name=None,\n                 auto_reset=False,\n                 fill_last_batch=None,\n                 dynamic_shape=False,\n                 last_batch_padded=False,\n                 last_batch_policy=LastBatchPolicy.FILL,\n                 prepare_first_batch=True):\n        super(DALIClassificationIterator, self).__init__(pipelines, [\"data\", \"label\"],\n                                                         size,\n                                                         reader_name=reader_name,\n                                                         auto_reset=auto_reset,\n                                                         fill_last_batch=fill_last_batch,\n                                                         dynamic_shape=dynamic_shape,\n                                                         last_batch_padded=last_batch_padded,\n                                                         last_batch_policy=last_batch_policy,\n                                                         prepare_first_batch=prepare_first_batch)\n\n\nclass TorchPythonFunction(ops.PythonFunctionBase):\n    schema_name = \"TorchPythonFunction\"\n    ops.register_cpu_op('TorchPythonFunction')\n    ops.register_gpu_op('TorchPythonFunction')\n\n    def _torch_stream_wrapper(self, function, *ins):\n        with torch.cuda.stream(self.stream):\n            out = function(*ins)\n        self.stream.synchronize()\n        return out\n\n    def torch_wrapper(self, batch_processing, function, device, *args):\n        func = function if device == 'cpu' else \\\n               lambda *ins: self._torch_stream_wrapper(function, *ins)\n        if batch_processing:\n            return ops.PythonFunction.function_wrapper_batch(func,\n                                                             self.num_outputs,\n                                                             torch.utils.dlpack.from_dlpack,\n                                                             torch.utils.dlpack.to_dlpack,\n                                                             *args)\n        else:\n            return ops.PythonFunction.function_wrapper_per_sample(func,\n                                                                  self.num_outputs,\n                                                                  torch_dlpack.from_dlpack,\n                                                                  torch_dlpack.to_dlpack,\n                                                                  *args)\n\n    def __call__(self, *inputs, **kwargs):\n        pipeline = Pipeline.current()\n        if pipeline is None:\n            Pipeline._raise_no_current_pipeline(\"TorchPythonFunction\")\n        if self.stream is None:\n            self.stream = torch.cuda.Stream(device=pipeline.device_id)\n        return super(TorchPythonFunction, self).__call__(*inputs, **kwargs)\n\n    def __init__(self, function, num_outputs=1, device='cpu', batch_processing=False, **kwargs):\n        self.stream = None\n        super(TorchPythonFunction, self).__init__(impl_name=\"DLTensorPythonFunctionImpl\",\n                                                  function=lambda *ins:\n                                                  self.torch_wrapper(batch_processing,\n                                                                     function, device,\n                                                                     *ins),\n                                                  num_outputs=num_outputs, device=device,\n                                                  batch_processing=batch_processing, **kwargs)\n\n\nops._wrap_op(TorchPythonFunction, \"fn\", __name__)","metadata":{"_kg_hide-input":true,"_kg_hide-output":true,"execution":{"iopub.status.busy":"2023-02-27T01:03:44.004811Z","iopub.execute_input":"2023-02-27T01:03:44.006038Z","iopub.status.idle":"2023-02-27T01:03:44.026388Z","shell.execute_reply.started":"2023-02-27T01:03:44.005993Z","shell.execute_reply":"2023-02-27T01:03:44.025507Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# Convert images (updated: DALI nightly)","metadata":{}},{"cell_type":"code","source":"import os\nimport sys\nimport cv2\nimport glob\nimport gdcm\nimport json\nimport shutil\nimport pydicom\nfrom pydicom.pixel_data_handlers.util import apply_voi_lut\nimport numpy as np\nimport pandas as pd\nimport seaborn as sns\nimport matplotlib.pyplot as plt\nfrom pathlib import Path\nimport gc\n\nfrom tqdm.notebook import tqdm\nfrom joblib import Parallel, delayed","metadata":{"execution":{"iopub.status.busy":"2023-02-27T01:03:44.027968Z","iopub.execute_input":"2023-02-27T01:03:44.028326Z","iopub.status.idle":"2023-02-27T01:03:44.881859Z","shell.execute_reply.started":"2023-02-27T01:03:44.028291Z","shell.execute_reply":"2023-02-27T01:03:44.88088Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"IMG_DIR = Path(\"/kaggle/input/rsna-breast-cancer-detection/test_images/\")\ntest_images = list(IMG_DIR.glob(\"*/*.dcm\"))\n\nDEBUG = len(test_images) == 4\n\nif DEBUG:\n    DEBUG_FOLD = 0\n    fold_df = pd.read_csv('/kaggle/input/rsna-misc/train_with_fold.csv')\n#     SAMPLE_ID = sorted(fold_df.query('fold==@DEBUG_FOLD').patient_id.unique().tolist())[:1000]\n    SAMPLE_ID = {\n        42624, 48001, 48514, 2179, 31107, 23554, 13185, 53255, \n        29192, 59530, 64908, 32527, 13845, 59552, 54816, 49954, \n        55330, 59307, 21934, 63536, 23729, 61490, 61874, 16955, \n        46014, 38727, 64456, 50375, 9162, 55755, 25550, 15696, \n        50002, 58195, 10198, 13016, 25050, 31581, 26333, 29664, \n        8289, 3305, 6637, 48493, 58610, 42231, 12282, 9083, 32252, 39677}\n    IMG_DIR = Path(\"/kaggle/input/rsna-breast-cancer-detection/train_images/\")\n    test_images = []\n    for pid in SAMPLE_ID:\n        test_images.extend(list(IMG_DIR.glob(f\"{pid}/*.dcm\")))\n    \nprint(\"Number of images :\", len(test_images))","metadata":{"execution":{"iopub.status.busy":"2023-02-27T01:03:44.884551Z","iopub.execute_input":"2023-02-27T01:03:44.884931Z","iopub.status.idle":"2023-02-27T01:03:45.178652Z","shell.execute_reply.started":"2023-02-27T01:03:44.884893Z","shell.execute_reply":"2023-02-27T01:03:45.17772Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"EXPORT_DIR = Path(\"/tmp/output/\")\nEXPORT_DIR.mkdir(exist_ok=True)\nSIZE = 2048\n\nif len(test_images) > 100:\n    N_CHUNKS = 4\nelse:\n    N_CHUNKS = 1\n\nCHUNKS = [(len(test_images) / N_CHUNKS * k, len(test_images) / N_CHUNKS * (k + 1)) for k in range(N_CHUNKS)]\nCHUNKS = np.array(CHUNKS).astype(int)\n    \nJ2K_FOLDER = Path(\"/tmp/j2k/\")","metadata":{"execution":{"iopub.status.busy":"2023-02-27T01:03:45.180263Z","iopub.execute_input":"2023-02-27T01:03:45.180633Z","iopub.status.idle":"2023-02-27T01:03:45.189057Z","shell.execute_reply.started":"2023-02-27T01:03:45.180596Z","shell.execute_reply":"2023-02-27T01:03:45.188102Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"import dicomsdl\nimport torch\nimport torch.nn.functional as F\nimport nvidia.dali.fn as fn\nimport nvidia.dali.types as types\nfrom nvidia.dali import pipeline_def\nfrom nvidia.dali.types import DALIDataType\nfrom pydicom.filebase import DicomBytesIO\nfrom nvidia.dali.plugin.pytorch import feed_ndarray, to_torch_type\n\n\ndef convert_dicom_to_j2k(file, save_folder):\n    patient, image = file.parents[0].stem, file.stem\n    dcmfile = pydicom.dcmread(str(file))\n    \n    if dcmfile.file_meta.TransferSyntaxUID == '1.2.840.10008.1.2.4.90':\n        with open(file, 'rb') as fp:\n            raw = DicomBytesIO(fp.read())\n            ds = pydicom.dcmread(raw)\n        offset = ds.PixelData.find(b\"\\x00\\x00\\x00\\x0C\")  #<---- the jpeg2000 header info we're looking for\n        hackedbitstream = bytearray()\n        hackedbitstream.extend(ds.PixelData[offset:])\n        with open(save_folder/f\"{patient}_{image}.jp2\", \"wb\") as binary_file:\n            binary_file.write(hackedbitstream)\n            \n    if dcmfile.file_meta.TransferSyntaxUID == '1.2.840.10008.1.2.4.70':\n        with open(file, 'rb') as fp:\n            raw = DicomBytesIO(fp.read())\n            ds = pydicom.dcmread(raw)\n        offset = ds.PixelData.find(b\"\\xff\\xd8\\xff\\xe0\")  #<---- the jpeg lossless header info we're looking for\n        hackedbitstream = bytearray()\n        hackedbitstream.extend(ds.PixelData[offset:])\n        with open(save_folder/f\"{patient}_{image}.jp2\", \"wb\") as binary_file:\n            binary_file.write(hackedbitstream)\n\n            \n@pipeline_def\ndef j2k_decode_pipeline(j2kfiles):\n    jpegs, _ = fn.readers.file(files=j2kfiles)\n    images = fn.experimental.decoders.image(jpegs, device='mixed', output_type=types.ANY_DATA, dtype=DALIDataType.UINT16)\n    return images\n\n\ndef load_image_pydicom(dataset, voi_lut=False, size=1024):\n    img = dataset.pixel_array\n    if voi_lut:\n        img = apply_voi_lut(img, dataset)\n    if dataset.PhotometricInterpretation == \"MONOCHROME1\":\n        img = np.max(img) - img\n    img = (img - img.min()) / (img.max() - img.min())\n    img = cv2.resize(img, (size, size))\n    return img\n\n\ndef load_image_dicomsdl(img_path, voi_lut=False, size=1024):\n    dataset = dicomsdl.open(img_path)\n    img = dataset.pixelData()\n    \n    if voi_lut:\n        # Load only the variables we need\n        center = dataset[\"WindowCenter\"]\n        width = dataset[\"WindowWidth\"]\n        bits_stored = dataset[\"BitsStored\"]\n        voi_lut_function = dataset[\"VOILUTFunction\"]\n\n        # For sigmoid it's a list, otherwise a single value\n        if isinstance(center, list):\n            center = center[0]\n        if isinstance(width, list):\n            width = width[0]\n\n        # Set y_min, max & range\n        y_min = 0\n        y_max = float(2**bits_stored - 1)\n        y_range = y_max\n\n        # Function with default LINEAR (so for Nan, it will use linear)\n        if voi_lut_function == \"SIGMOID\":\n            img = y_range / (1 + np.exp(-4 * (img - center) / width)) + y_min\n        else:\n            # Checks width for < 1 (in our case not necessary, always >= 750)\n            center -= 0.5\n            width -= 1\n\n            below = img <= (center - width / 2)\n            above = img > (center + width / 2)\n            between = np.logical_and(~below, ~above)\n\n            img[below] = y_min\n            img[above] = y_max\n            if between.any():\n                img[between] = (\n                    ((img[between] - center) / width + 0.5) * y_range + y_min\n                )\n    \n    if dataset[\"PhotometricInterpretation\"] == \"MONOCHROME1\":\n        img = np.max(img) - img\n    \n    img = (img - img.min()) / (img.max() - img.min())\n    img = cv2.resize(img, (size, size))\n    \n    return img\n\n\ndef torch_voi_lut(img, dataset):\n    center = dataset[\"WindowCenter\"]\n    width = dataset[\"WindowWidth\"]\n    bits_stored = dataset[\"BitsStored\"]\n    voi_lut_function = dataset[\"VOILUTFunction\"]\n    # For sigmoid it's a list, otherwise a single value\n    if isinstance(center, list):\n        center = center[0]\n    if isinstance(width, list):\n        width = width[0]\n    # Set y_min, max & range\n    y_min = 0\n    y_max = float(2**bits_stored - 1)\n    y_range = y_max\n\n    # Function with default LINEAR (so for Nan, it will use linear)\n    if voi_lut_function == \"SIGMOID\":\n        img = y_range / (1 + torch.exp(-4 * (img - center) / width)) + y_min\n    else:\n        # Checks width for < 1 (in our case not necessary, always >= 750)\n        center -= 0.5\n        width -= 1\n\n        below = img <= (center - width / 2)\n        above = img > (center + width / 2)\n        between = torch.logical_and(~below, ~above)\n\n        img[below] = y_min\n        img[above] = y_max\n        if between.any():\n            img[between] = (\n                ((img[between] - center) / width + 0.5) * y_range + y_min\n            )\n    return img","metadata":{"_kg_hide-input":true,"execution":{"iopub.status.busy":"2023-02-27T01:03:45.191396Z","iopub.execute_input":"2023-02-27T01:03:45.191953Z","iopub.status.idle":"2023-02-27T01:03:47.062547Z","shell.execute_reply.started":"2023-02-27T01:03:45.191918Z","shell.execute_reply":"2023-02-27T01:03:47.061569Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"for chunk in tqdm(CHUNKS):\n    J2K_FOLDER.mkdir(exist_ok=True)\n\n    _ = Parallel(n_jobs=2)(\n        delayed(convert_dicom_to_j2k)(img, save_folder=J2K_FOLDER)\n        for img in test_images[chunk[0]: chunk[1]]\n    )\n    \n    j2kfiles = list(J2K_FOLDER.glob(\"*.jp2\"))\n\n    if not len(j2kfiles):\n        continue\n\n    pipe = j2k_decode_pipeline(j2kfiles, batch_size=1, num_threads=2, device_id=0, debug=True)\n    pipe.build()\n\n    for i, f in enumerate(j2kfiles):\n        patient, image = f.stem.split('_')\n        dicom = dicomsdl.open(str(IMG_DIR/f\"{patient}/{image}.dcm\"))\n        \n        try:\n            out = pipe.run()\n\n            # Dali -> Torch\n            img = out[0][0]\n            img_torch = torch.empty(img.shape(), dtype=torch.int16, device=\"cuda\")\n            feed_ndarray(img, img_torch, cuda_stream=torch.cuda.current_stream(device=0))\n            img = img_torch.float()\n\n            # Scale, resize, invert on GPU !\n            img = torch_voi_lut(img, dicom)\n\n            if dicom.PhotometricInterpretation == \"MONOCHROME1\":\n                img = img.amax() - img\n\n            min_, max_ = img.amin(), img.amax()\n            img = (img - min_) / (max_ - min_)\n\n            if SIZE:\n                img = F.interpolate(\n                    img.view(1, 1, img.size(0), img.size(1)), (SIZE, SIZE), mode=\"bilinear\")[0, 0]\n\n            # Back to CPU + SAVE\n            img = (img * 255).cpu().numpy().astype(np.uint8)\n\n            cv2.imwrite(str(EXPORT_DIR /f\"{patient}_{image}.png\"), img)\n        \n        except Exception as e:\n\n            print(i, e)\n            pipe = j2k_decode_pipeline(j2kfiles[i+1:], batch_size=1, num_threads=2, device_id=0, debug=True)\n            pipe.build()\n            continue\n\n    shutil.rmtree(J2K_FOLDER)","metadata":{"_kg_hide-input":true,"execution":{"iopub.status.busy":"2023-02-27T01:03:47.064097Z","iopub.execute_input":"2023-02-27T01:03:47.06478Z","iopub.status.idle":"2023-02-27T01:04:46.4914Z","shell.execute_reply.started":"2023-02-27T01:03:47.064742Z","shell.execute_reply":"2023-02-27T01:04:46.490259Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"fns = glob.glob(f'{EXPORT_DIR}/*.png')\nn_saved = len(fns)\nprint(f'Image on disk count : {n_saved}')","metadata":{"execution":{"iopub.status.busy":"2023-02-27T01:04:46.492998Z","iopub.execute_input":"2023-02-27T01:04:46.494068Z","iopub.status.idle":"2023-02-27T01:04:46.501832Z","shell.execute_reply.started":"2023-02-27T01:04:46.494025Z","shell.execute_reply":"2023-02-27T01:04:46.500852Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"gpu_processed_files = [fn.split('/')[-1].replace('_','/').replace('png','dcm') for fn in fns]\nto_process_images = [f for f in test_images if '/'.join(str(f).split('/')[-2:]) not in gpu_processed_files]\nlen(gpu_processed_files), len(to_process_images)","metadata":{"execution":{"iopub.status.busy":"2023-02-27T01:04:46.503382Z","iopub.execute_input":"2023-02-27T01:04:46.504102Z","iopub.status.idle":"2023-02-27T01:04:47.028585Z","shell.execute_reply.started":"2023-02-27T01:04:46.50406Z","shell.execute_reply":"2023-02-27T01:04:47.02746Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"def process(f, size=2048, save_folder=\"\"):\n    patient, image = f.parents[0].stem, f.stem\n\n    dicom = pydicom.dcmread(f)\n\n    try:\n        img = load_image_dicomsdl(f, voi_lut=True, size=size)\n    except:\n        img = load_image_pydicom(dicom, voi_lut=True, size=size)\n\n    cv2.imwrite(str(save_folder/f\"{patient}_{image}.png\"), (img * 255).astype(np.uint8))","metadata":{"execution":{"iopub.status.busy":"2023-02-27T01:04:47.033708Z","iopub.execute_input":"2023-02-27T01:04:47.034636Z","iopub.status.idle":"2023-02-27T01:04:47.041277Z","shell.execute_reply.started":"2023-02-27T01:04:47.034599Z","shell.execute_reply":"2023-02-27T01:04:47.040339Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"_ = Parallel(n_jobs=2)(\n    delayed(process)(img, size=SIZE, save_folder=EXPORT_DIR)\n    for img in tqdm(to_process_images)\n)","metadata":{"execution":{"iopub.status.busy":"2023-02-27T01:04:47.043061Z","iopub.execute_input":"2023-02-27T01:04:47.043525Z","iopub.status.idle":"2023-02-27T01:04:47.084766Z","shell.execute_reply.started":"2023-02-27T01:04:47.043466Z","shell.execute_reply":"2023-02-27T01:04:47.083875Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# ====== CODE FROM HERE =====\n# Load kuma env","metadata":{}},{"cell_type":"code","source":"!cp ../input/rsna-mammo-2023/dataset/*.py ./\n!ln -s ../input/rsna-mammo-2023/dataset/kuma_utils/ \n!ln -s ../input/rsna-mammo-2023/dataset/timm/ \n!ln -s ../input/rsna-mammo-2023/dataset/iterstrat/ \n!ln -s ../input/rsna-mammo-2023/dataset/segmentation_models_pytorch/ \n!ln -s ../input/rsna-mammo-2023/dataset/global_objectives/\n\nfrom configs import *\nfrom kuma_utils.utils import sigmoid\nfrom timm.layers import convert_sync_batchnorm\nfrom metrics import Pfbeta, PercentilePfbeta\n\nfrom torch.cuda import amp\nfrom tqdm.auto import tqdm\nimport pickle","metadata":{"execution":{"iopub.status.busy":"2023-02-27T01:04:47.085904Z","iopub.execute_input":"2023-02-27T01:04:47.086606Z","iopub.status.idle":"2023-02-27T01:04:56.843774Z","shell.execute_reply.started":"2023-02-27T01:04:47.086571Z","shell.execute_reply":"2023-02-27T01:04:56.842656Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# Load Ishikei env","metadata":{}},{"cell_type":"code","source":"!cp ../input/ishikei-mammo/*.py ./\nfrom ishikei_configs import *","metadata":{"execution":{"iopub.status.busy":"2023-02-27T01:04:56.845784Z","iopub.execute_input":"2023-02-27T01:04:56.846155Z","iopub.status.idle":"2023-02-27T01:04:57.886348Z","shell.execute_reply.started":"2023-02-27T01:04:56.846115Z","shell.execute_reply":"2023-02-27T01:04:57.885127Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# Load Ariyasu env","metadata":{}},{"cell_type":"code","source":"!pip install /kaggle/input/einops-download/einops-0.6.0-py3-none-any.whl > /dev/null\n\nfrom functools import partial\n\nimport torch\nimport torch.nn.functional as F\nimport torch.utils.checkpoint as checkpoint\nfrom einops import rearrange\nimport timm\nfrom timm.models.layers import DropPath, trunc_normal_\nfrom timm.models import register_model\nfrom torch import nn\nimport warnings\nwarnings.simplefilter('ignore')\n\ndef merge_pre_bn(module, pre_bn_1, pre_bn_2=None):\n    \"\"\" Merge pre BN to reduce inference runtime.\n    \"\"\"\n    weight = module.weight.data\n    if module.bias is None:\n        zeros = torch.zeros(module.out_channels, device=weight.device).type(weight.type())\n        module.bias = nn.Parameter(zeros)\n    bias = module.bias.data\n    if pre_bn_2 is None:\n        assert pre_bn_1.track_running_stats is True, \"Unsupport bn_module.track_running_stats is False\"\n        assert pre_bn_1.affine is True, \"Unsupport bn_module.affine is False\"\n\n        scale_invstd = pre_bn_1.running_var.add(pre_bn_1.eps).pow(-0.5)\n        extra_weight = scale_invstd * pre_bn_1.weight\n        extra_bias = pre_bn_1.bias - pre_bn_1.weight * pre_bn_1.running_mean * scale_invstd\n    else:\n        assert pre_bn_1.track_running_stats is True, \"Unsupport bn_module.track_running_stats is False\"\n        assert pre_bn_1.affine is True, \"Unsupport bn_module.affine is False\"\n\n        assert pre_bn_2.track_running_stats is True, \"Unsupport bn_module.track_running_stats is False\"\n        assert pre_bn_2.affine is True, \"Unsupport bn_module.affine is False\"\n\n        scale_invstd_1 = pre_bn_1.running_var.add(pre_bn_1.eps).pow(-0.5)\n        scale_invstd_2 = pre_bn_2.running_var.add(pre_bn_2.eps).pow(-0.5)\n\n        extra_weight = scale_invstd_1 * pre_bn_1.weight * scale_invstd_2 * pre_bn_2.weight\n        extra_bias = scale_invstd_2 * pre_bn_2.weight *(pre_bn_1.bias - pre_bn_1.weight * pre_bn_1.running_mean * scale_invstd_1 - pre_bn_2.running_mean) + pre_bn_2.bias\n\n    if isinstance(module, nn.Linear):\n        extra_bias = weight @ extra_bias\n        weight.mul_(extra_weight.view(1, weight.size(1)).expand_as(weight))\n    elif isinstance(module, nn.Conv2d):\n        assert weight.shape[2] == 1 and weight.shape[3] == 1\n        weight = weight.reshape(weight.shape[0], weight.shape[1])\n        extra_bias = weight @ extra_bias\n        weight.mul_(extra_weight.view(1, weight.size(1)).expand_as(weight))\n        weight = weight.reshape(weight.shape[0], weight.shape[1], 1, 1)\n    bias.add_(extra_bias)\n\n    module.weight.data = weight\n    module.bias.data = bias\n\n\n\nNORM_EPS = 1e-5\n\n\nclass ConvBNReLU(nn.Module):\n    def __init__(\n            self,\n            in_channels,\n            out_channels,\n            kernel_size,\n            stride,\n            groups=1):\n        super(ConvBNReLU, self).__init__()\n        self.conv = nn.Conv2d(in_channels, out_channels, kernel_size=kernel_size, stride=stride,\n                              padding=1, groups=groups, bias=False)\n        self.norm = nn.BatchNorm2d(out_channels, eps=NORM_EPS)\n        self.act = nn.ReLU(inplace=True)\n\n    def forward(self, x):\n        x = self.conv(x)\n        x = self.norm(x)\n        x = self.act(x)\n        return x\n\n\ndef _make_divisible(v, divisor, min_value=None):\n    if min_value is None:\n        min_value = divisor\n    new_v = max(min_value, int(v + divisor / 2) // divisor * divisor)\n    # Make sure that round down does not go down by more than 10%.\n    if new_v < 0.9 * v:\n        new_v += divisor\n    return new_v\n\n\nclass PatchEmbed(nn.Module):\n    def __init__(self,\n                 in_channels,\n                 out_channels,\n                 stride=1):\n        super(PatchEmbed, self).__init__()\n        norm_layer = partial(nn.BatchNorm2d, eps=NORM_EPS)\n        if stride == 2:\n            self.avgpool = nn.AvgPool2d((2, 2), stride=2, ceil_mode=True, count_include_pad=False)\n            self.conv = nn.Conv2d(in_channels, out_channels, kernel_size=1, stride=1, bias=False)\n            self.norm = norm_layer(out_channels)\n        elif in_channels != out_channels:\n            self.avgpool = nn.Identity()\n            self.conv = nn.Conv2d(in_channels, out_channels, kernel_size=1, stride=1, bias=False)\n            self.norm = norm_layer(out_channels)\n        else:\n            self.avgpool = nn.Identity()\n            self.conv = nn.Identity()\n            self.norm = nn.Identity()\n\n    def forward(self, x):\n        return self.norm(self.conv(self.avgpool(x)))\n\n\nclass MHCA(nn.Module):\n    \"\"\"\n    Multi-Head Convolutional Attention\n    \"\"\"\n    def __init__(self, out_channels, head_dim):\n        super(MHCA, self).__init__()\n        norm_layer = partial(nn.BatchNorm2d, eps=NORM_EPS)\n        self.group_conv3x3 = nn.Conv2d(out_channels, out_channels, kernel_size=3, stride=1,\n                                       padding=1, groups=out_channels // head_dim, bias=False)\n        self.norm = norm_layer(out_channels)\n        self.act = nn.ReLU(inplace=True)\n        self.projection = nn.Conv2d(out_channels, out_channels, kernel_size=1, bias=False)\n\n    def forward(self, x):\n        out = self.group_conv3x3(x)\n        out = self.norm(out)\n        out = self.act(out)\n        out = self.projection(out)\n        return out\n\n\nclass Mlp(nn.Module):\n    def __init__(self, in_features, out_features=None, mlp_ratio=None, drop=0., bias=True):\n        super().__init__()\n        out_features = out_features or in_features\n        hidden_dim = _make_divisible(in_features * mlp_ratio, 32)\n        self.conv1 = nn.Conv2d(in_features, hidden_dim, kernel_size=1, bias=bias)\n        self.act = nn.ReLU(inplace=True)\n        self.conv2 = nn.Conv2d(hidden_dim, out_features, kernel_size=1, bias=bias)\n        self.drop = nn.Dropout(drop)\n\n    def merge_bn(self, pre_norm):\n        merge_pre_bn(self.conv1, pre_norm)\n\n    def forward(self, x):\n        x = self.conv1(x)\n        x = self.act(x)\n        x = self.drop(x)\n        x = self.conv2(x)\n        x = self.drop(x)\n        return x\n\n\nclass NCB(nn.Module):\n    \"\"\"\n    Next Convolution Block\n    \"\"\"\n    def __init__(self, in_channels, out_channels, stride=1, path_dropout=0,\n                 drop=0, head_dim=32, mlp_ratio=3):\n        super(NCB, self).__init__()\n        self.in_channels = in_channels\n        self.out_channels = out_channels\n        norm_layer = partial(nn.BatchNorm2d, eps=NORM_EPS)\n        assert out_channels % head_dim == 0\n\n        self.patch_embed = PatchEmbed(in_channels, out_channels, stride)\n        self.mhca = MHCA(out_channels, head_dim)\n        self.attention_path_dropout = DropPath(path_dropout)\n\n        self.norm = norm_layer(out_channels)\n        self.mlp = Mlp(out_channels, mlp_ratio=mlp_ratio, drop=drop, bias=True)\n        self.mlp_path_dropout = DropPath(path_dropout)\n        self.is_bn_merged = False\n\n    def merge_bn(self):\n        if not self.is_bn_merged:\n            self.mlp.merge_bn(self.norm)\n            self.is_bn_merged = True\n\n    def forward(self, x):\n        x = self.patch_embed(x)\n        x = x + self.attention_path_dropout(self.mhca(x))\n        if not torch.onnx.is_in_onnx_export() and not self.is_bn_merged:\n            out = self.norm(x)\n        else:\n            out = x\n        x = x + self.mlp_path_dropout(self.mlp(out))\n        return x\n\n\nclass E_MHSA(nn.Module):\n    \"\"\"\n    Efficient Multi-Head Self Attention\n    \"\"\"\n    def __init__(self, dim, out_dim=None, head_dim=32, qkv_bias=True, qk_scale=None,\n                 attn_drop=0, proj_drop=0., sr_ratio=1):\n        super().__init__()\n        self.dim = dim\n        self.out_dim = out_dim if out_dim is not None else dim\n        self.num_heads = self.dim // head_dim\n        self.scale = qk_scale or head_dim ** -0.5\n        self.q = nn.Linear(dim, self.dim, bias=qkv_bias)\n        self.k = nn.Linear(dim, self.dim, bias=qkv_bias)\n        self.v = nn.Linear(dim, self.dim, bias=qkv_bias)\n        self.proj = nn.Linear(self.dim, self.out_dim)\n        self.attn_drop = nn.Dropout(attn_drop)\n        self.proj_drop = nn.Dropout(proj_drop)\n\n        self.sr_ratio = sr_ratio\n        self.N_ratio = sr_ratio ** 2\n        if sr_ratio > 1:\n            self.sr = nn.AvgPool1d(kernel_size=self.N_ratio, stride=self.N_ratio)\n            self.norm = nn.BatchNorm1d(dim, eps=NORM_EPS)\n        self.is_bn_merged = False\n\n    def merge_bn(self, pre_bn):\n        merge_pre_bn(self.q, pre_bn)\n        if self.sr_ratio > 1:\n            merge_pre_bn(self.k, pre_bn, self.norm)\n            merge_pre_bn(self.v, pre_bn, self.norm)\n        else:\n            merge_pre_bn(self.k, pre_bn)\n            merge_pre_bn(self.v, pre_bn)\n        self.is_bn_merged = True\n\n    def forward(self, x):\n        B, N, C = x.shape\n        q = self.q(x)\n        q = q.reshape(B, N, self.num_heads, int(C // self.num_heads)).permute(0, 2, 1, 3)\n\n        if self.sr_ratio > 1:\n            x_ = x.transpose(1, 2)\n            x_ = self.sr(x_)\n            if not torch.onnx.is_in_onnx_export() and not self.is_bn_merged:\n                x_ = self.norm(x_)\n            x_ = x_.transpose(1, 2)\n            k = self.k(x_)\n            k = k.reshape(B, -1, self.num_heads, int(C // self.num_heads)).permute(0, 2, 3, 1)\n            v = self.v(x_)\n            v = v.reshape(B, -1, self.num_heads, int(C // self.num_heads)).permute(0, 2, 1, 3)\n        else:\n            k = self.k(x)\n            k = k.reshape(B, -1, self.num_heads, int(C // self.num_heads)).permute(0, 2, 3, 1)\n            v = self.v(x)\n            v = v.reshape(B, -1, self.num_heads, int(C // self.num_heads)).permute(0, 2, 1, 3)\n        attn = (q @ k) * self.scale\n\n        attn = attn.softmax(dim=-1)\n        attn = self.attn_drop(attn)\n\n        x = (attn @ v).transpose(1, 2).reshape(B, N, C)\n        x = self.proj(x)\n        x = self.proj_drop(x)\n        return x\n\n\nclass NTB(nn.Module):\n    \"\"\"\n    Next Transformer Block\n    \"\"\"\n    def __init__(\n            self, in_channels, out_channels, path_dropout, stride=1, sr_ratio=1,\n            mlp_ratio=2, head_dim=32, mix_block_ratio=0.75, attn_drop=0, drop=0,\n    ):\n        super(NTB, self).__init__()\n        self.in_channels = in_channels\n        self.out_channels = out_channels\n        self.mix_block_ratio = mix_block_ratio\n        norm_func = partial(nn.BatchNorm2d, eps=NORM_EPS)\n\n        self.mhsa_out_channels = _make_divisible(int(out_channels * mix_block_ratio), 32)\n        self.mhca_out_channels = out_channels - self.mhsa_out_channels\n\n        self.patch_embed = PatchEmbed(in_channels, self.mhsa_out_channels, stride)\n        self.norm1 = norm_func(self.mhsa_out_channels)\n        self.e_mhsa = E_MHSA(self.mhsa_out_channels, head_dim=head_dim, sr_ratio=sr_ratio,\n                             attn_drop=attn_drop, proj_drop=drop)\n        self.mhsa_path_dropout = DropPath(path_dropout * mix_block_ratio)\n\n        self.projection = PatchEmbed(self.mhsa_out_channels, self.mhca_out_channels, stride=1)\n        self.mhca = MHCA(self.mhca_out_channels, head_dim=head_dim)\n        self.mhca_path_dropout = DropPath(path_dropout * (1 - mix_block_ratio))\n\n        self.norm2 = norm_func(out_channels)\n        self.mlp = Mlp(out_channels, mlp_ratio=mlp_ratio, drop=drop)\n        self.mlp_path_dropout = DropPath(path_dropout)\n\n        self.is_bn_merged = False\n\n    def merge_bn(self):\n        if not self.is_bn_merged:\n            self.e_mhsa.merge_bn(self.norm1)\n            self.mlp.merge_bn(self.norm2)\n            self.is_bn_merged = True\n\n    def forward(self, x):\n        x = self.patch_embed(x)\n        B, C, H, W = x.shape\n        if not torch.onnx.is_in_onnx_export() and not self.is_bn_merged:\n            out = self.norm1(x)\n        else:\n            out = x\n        out = rearrange(out, \"b c h w -> b (h w) c\")  # b n c\n        out = self.mhsa_path_dropout(self.e_mhsa(out))\n        x = x + rearrange(out, \"b (h w) c -> b c h w\", h=H)\n\n        out = self.projection(x)\n        out = out + self.mhca_path_dropout(self.mhca(out))\n        x = torch.cat([x, out], dim=1)\n\n        if not torch.onnx.is_in_onnx_export() and not self.is_bn_merged:\n            out = self.norm2(x)\n        else:\n            out = x\n        x = x + self.mlp_path_dropout(self.mlp(out))\n        return x\n\n\nclass NextViT(nn.Module):\n    def __init__(self, stem_chs, depths, path_dropout, attn_drop=0, drop=0, num_classes=1000,\n                 strides=[1, 2, 2, 2], sr_ratios=[8, 4, 2, 1], head_dim=32, mix_block_ratio=0.75,\n                 use_checkpoint=False, pretrained_cfg_overlay=None):\n        super(NextViT, self).__init__()\n        self.use_checkpoint = use_checkpoint\n\n        self.stage_out_channels = [[96] * (depths[0]),\n                                   [192] * (depths[1] - 1) + [256],\n                                   [384, 384, 384, 384, 512] * (depths[2] // 5),\n                                   [768] * (depths[3] - 1) + [1024]]\n\n        # Next Hybrid Strategy\n        self.stage_block_types = [[NCB] * depths[0],\n                                  [NCB] * (depths[1] - 1) + [NTB],\n                                  [NCB, NCB, NCB, NCB, NTB] * (depths[2] // 5),\n                                  [NCB] * (depths[3] - 1) + [NTB]]\n\n        self.stem = nn.Sequential(\n            ConvBNReLU(3, stem_chs[0], kernel_size=3, stride=2),\n            ConvBNReLU(stem_chs[0], stem_chs[1], kernel_size=3, stride=1),\n            ConvBNReLU(stem_chs[1], stem_chs[2], kernel_size=3, stride=1),\n            ConvBNReLU(stem_chs[2], stem_chs[2], kernel_size=3, stride=2),\n        )\n        input_channel = stem_chs[-1]\n        features = []\n        idx = 0\n        dpr = [x.item() for x in torch.linspace(0, path_dropout, sum(depths))]  # stochastic depth decay rule\n        for stage_id in range(len(depths)):\n            numrepeat = depths[stage_id]\n            output_channels = self.stage_out_channels[stage_id]\n            block_types = self.stage_block_types[stage_id]\n            for block_id in range(numrepeat):\n                if strides[stage_id] == 2 and block_id == 0:\n                    stride = 2\n                else:\n                    stride = 1\n                output_channel = output_channels[block_id]\n                block_type = block_types[block_id]\n                if block_type is NCB:\n                    layer = NCB(input_channel, output_channel, stride=stride, path_dropout=dpr[idx + block_id],\n                                drop=drop, head_dim=head_dim)\n                    features.append(layer)\n                elif block_type is NTB:\n                    layer = NTB(input_channel, output_channel, path_dropout=dpr[idx + block_id], stride=stride,\n                                sr_ratio=sr_ratios[stage_id], head_dim=head_dim, mix_block_ratio=mix_block_ratio,\n                                attn_drop=attn_drop, drop=drop)\n                    features.append(layer)\n                input_channel = output_channel\n            idx += numrepeat\n        self.features = nn.Sequential(*features)\n\n        self.norm = nn.BatchNorm2d(output_channel, eps=NORM_EPS)\n\n        self.avgpool = nn.AdaptiveAvgPool2d((1, 1))\n        self.proj_head = nn.Sequential(\n            nn.Linear(output_channel, num_classes),\n        )\n\n        self.stage_out_idx = [sum(depths[:idx + 1]) - 1 for idx in range(len(depths))]\n#         print('initialize_weights...')\n        self._initialize_weights()\n\n    def merge_bn(self):\n        self.eval()\n        for idx, module in self.named_modules():\n            if isinstance(module, NCB) or isinstance(module, NTB):\n                module.merge_bn()\n\n    def _initialize_weights(self):\n        for n, m in self.named_modules():\n            if isinstance(m, (nn.BatchNorm2d, nn.GroupNorm, nn.LayerNorm, nn.BatchNorm1d)):\n                nn.init.constant_(m.weight, 1.0)\n                nn.init.constant_(m.bias, 0)\n            elif isinstance(m, nn.Linear):\n                trunc_normal_(m.weight, std=.02)\n                if hasattr(m, 'bias') and m.bias is not None:\n                    nn.init.constant_(m.bias, 0)\n            elif isinstance(m, nn.Conv2d):\n                trunc_normal_(m.weight, std=.02)\n                if hasattr(m, 'bias') and m.bias is not None:\n                    nn.init.constant_(m.bias, 0)\n\n    def forward(self, x):\n        x = self.stem(x)\n        for idx, layer in enumerate(self.features):\n            if self.use_checkpoint:\n                x = checkpoint.checkpoint(layer, x)\n            else:\n                x = layer(x)\n        x = self.norm(x)\n        x = self.avgpool(x)\n        x = torch.flatten(x, 1)\n        x = self.proj_head(x)\n        return x\n\n\n@register_model\ndef nextvit_small(pretrained=False, pretrained_cfg=None, **kwargs):\n    model = NextViT(stem_chs=[64, 32, 64], depths=[3, 4, 10, 3], path_dropout=0.1, **kwargs)\n    return model\n\n\n@register_model\ndef nextvit_base(pretrained=False, pretrained_cfg=None, **kwargs):\n    model = NextViT(stem_chs=[64, 32, 64], depths=[3, 4, 20, 3], path_dropout=0.2, **kwargs)\n    return model\n\n\n@register_model\ndef nextvit_large(pretrained=False, pretrained_cfg=None, **kwargs):\n    model = NextViT(stem_chs=[64, 32, 64], depths=[3, 4, 30, 3], path_dropout=0.2, **kwargs)\n    return model\n\nclass NextVitNet(nn.Module):\n    def __init__(self, model_name, pretrained=True, num_classes=1, pretrained_cfg_overlay=None):\n        super(NextVitNet, self).__init__()\n        # self.register_buffer('mean', torch.FloatTensor([0.5, 0.5, 0.5]).reshape(1, 3, 1, 1))\n        # self.register_buffer('std', torch.FloatTensor([0.5, 0.5, 0.5]).reshape(1, 3, 1, 1))\n        self.encoder = timm.create_model(model_name)\n        self.encoder.proj_head = nn.Linear(1024, num_classes)\n        # self.encoder.norm = nn.Identity()\n        # self.encoder.avgpool = nn.Identity()\n        # self.encoder.proj_head = nn.Identity()\n        # self.fc = nn.Linear(1024,1)\n\n    def forward(self, x):\n        # x = (x - self.mean) / self.std\n        x = self.encoder(x)\n        # x = F.adaptive_avg_pool2d(x,1)\n        # x = torch.flatten(x,1,3)\n        # x = self.fc(x).reshape(-1)\n        return x\n    \n    \nfrom torch.utils.data import DataLoader, Dataset\nimport time\nimport albumentations as A\nfrom albumentations.pytorch import ToTensorV2\n\nclass RSNADatasetAriyasu(Dataset):\n    def __init__(self, paths, cfg):\n        self.paths = paths\n        self.transforms = cfg.transforms\n        self.reduce_0_area_th = cfg.reduce_0_area_th\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)[:,:,0]\n\n        reduce_0_area_th = self.reduce_0_area_th\n        image = image[:, image.mean(0)>reduce_0_area_th]\n        image = image[image.mean(1)>reduce_0_area_th]\n        image = image[:, image.mean(0)>reduce_0_area_th]\n        image = image[image.mean(1)>reduce_0_area_th]\n\n        if image.sum()==0:\n            image = cv2.imread(path)[:,:,0]\n        image = np.array([image, image, image]).transpose((1,2,0))\n\n        image = self.transforms(image=image)['image']\n        return image\n\n# configs\n\n\nclass ariyasu_nano_config():\n    def __init__(self):\n        self.model_names = ['convnext_nano.in12k_ft_in1k']*4\n        self.batch_size = 8\n        self.num_classes = 3\n        self.reduce_0_area_th = 18\n        self.tta = 1\n        self.transforms = A.Compose([\n            A.Resize(1536, 768),\n            A.Normalize(),\n            ToTensorV2(),\n        ])\n        self.model_paths = [f'/kaggle/input/distill-mse-temp2-mixup-heavy-aug-temp3/last_fold{fold}.ckpt' for fold in range(4)]\n        self.file_name = 'distill_convnext_nano_oof'\n\nclass ariyasu_large_config():\n    def __init__(self):\n        self.model_names = ['convnext_large.fb_in22k_ft_in1k_384']*4\n        self.batch_size = 2\n        self.num_classes = 3\n        self.reduce_0_area_th = 18\n        self.tta = 1\n        self.transforms = A.Compose([\n            A.Resize(1536, 768),\n            A.Normalize(),\n            ToTensorV2(),\n        ])\n        if DEBUG:\n            self.model_paths = [f'/kaggle/input/large-kumadata-1536/last_fold{DEBUG_FOLD}.ckpt' for fold in range(4)]\n        else:\n            self.model_paths = [f'/kaggle/input/large-kumadata-1536/last_fold{fold}.ckpt' for fold in range(4)]\n        self.file_name = 'convnext_large_oof'\n\nclass ariyasu_xlarge_config():\n    def __init__(self):\n        self.model_names = ['convnext_xlarge.fb_in22k_ft_in1k_384']*4\n        self.batch_size = 2\n        self.num_classes = 3\n        self.reduce_0_area_th = 18\n        self.tta = 1\n        self.transforms = A.Compose([\n            A.Resize(1536, 768),\n            A.Normalize(),\n            ToTensorV2(),\n        ])\n        if DEBUG:\n            self.model_paths = [f'/kaggle/input/xlarge-kumadata-1536/last_fold{DEBUG_FOLD}.ckpt' for fold in range(4)]\n        else:\n            self.model_paths = [f'/kaggle/input/xlarge-kumadata-1536/last_fold{fold}.ckpt' for fold in range(4)]\n        self.file_name = 'convnext_xlarge_oof'\n\nclass ariyasu_nextvit_base_config():\n    def __init__(self):\n        self.model_names = ['nextvit_base']*4\n        self.batch_size = 4\n        self.num_classes = 3\n        self.reduce_0_area_th = 18\n        self.tta = 1\n        self.transforms = A.Compose([\n            A.Resize(1536, 768),\n            A.Normalize(),\n            ToTensorV2(),\n        ])\n        if DEBUG:\n            self.model_paths = [f'/kaggle/input/nextvit-base-kumadata-1536/last_fold{DEBUG_FOLD}.ckpt' for fold in range(4)]\n        else:\n            self.model_paths = [f'/kaggle/input/nextvit-base-kumadata-1536/last_fold{fold}.ckpt' for fold in range(4)]\n        self.file_name = 'nextvit_base_oof'","metadata":{"_kg_hide-input":true,"_kg_hide-output":true,"execution":{"iopub.status.busy":"2023-02-27T01:04:57.888285Z","iopub.execute_input":"2023-02-27T01:04:57.888867Z","iopub.status.idle":"2023-02-27T01:05:27.386014Z","shell.execute_reply.started":"2023-02-27T01:04:57.888831Z","shell.execute_reply":"2023-02-27T01:05:27.38486Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# load Charm env","metadata":{}},{"cell_type":"code","source":"import sys\nsys.path.append('/kaggle/input/rsna-mammo-charm-modules')\n!pip install /kaggle/input/omegaconf/omegaconf-2.0.5-py3-none-any.whl\n!ln -s ../input/rsna-mammo-2023/dataset/timm/ \n\nimport yaml\nfrom typing import Dict, Tuple\nfrom pathlib import Path\nfrom collections import defaultdict\nfrom tqdm.auto import tqdm\n\n\nimport albumentations as A\nfrom albumentations.pytorch.transforms import ToTensorV2\nimport cv2\nimport torch\nimport torch.nn as nn\nimport torch.nn.functional as F\nfrom torch import Tensor\nfrom torch.utils.data import Dataset, DataLoader\nfrom omegaconf import DictConfig, OmegaConf\n\nfrom src.nn.backbone import load_backbone\nfrom src.nn.backbones.base import BackboneBase\nfrom src.nn.pool.pool import ChannelWiseGeM, GeM\n\ndef get_weights_to_load(model: nn.Module, ckpt: Dict[str, Tensor]) -> Dict[str, Tensor]:\n    model_dict = model.state_dict()\n    for ckpt_key, ckpt_weight in ckpt.items():\n        if ckpt_key not in model_dict:\n            pass\n        else:\n            if ckpt_weight.size() != model_dict[ckpt_key].size():\n                pass\n            else:\n                model_dict[ckpt_key] = ckpt_weight\n    return model_dict\n\n\ndef init_model_from_config(cfg: DictConfig, weight_path: str):\n    model = nn.Sequential()\n    backbone = init_backbone(cfg, pretrained=False)\n    forward_features = nn.Sequential()\n\n    forward_features.add_module(\"backbone\", backbone)\n    if cfg.pool.type == \"adaptive\":\n        forward_features.add_module(\"pool\", nn.AdaptiveAvgPool2d((1, 1)))\n        forward_features.add_module(\"flatten\", nn.Flatten())\n    elif cfg.pool.type == \"gem\":\n        forward_features.add_module(\n            \"pool\", GeM(p=cfg.pool.p, p_trainable=cfg.pool.p_trainable)\n        )\n        forward_features.add_module(\"flatten\", nn.Flatten())\n    elif cfg.pool.type == \"gem_ch\":\n        forward_features.add_module(\n            \"pool\",\n            ChannelWiseGeM(\n                dim=backbone.out_features,\n                p=cfg.pool.p,\n                requires_grad=cfg.pool.p_trainable,\n            ),\n        )\n        forward_features.add_module(\"flatten\", nn.Flatten())\n\n    if cfg.use_bn:\n        forward_features.add_module(\"normalize\", nn.BatchNorm1d(backbone.out_features))\n        forward_features.add_module(\"relu\", torch.nn.PReLU())\n\n    model.add_module(\"forward_features\", forward_features)\n    if cfg.head.type == \"linear\":\n        out_features = backbone.out_features\n        if cfg.use_multi_view:\n            out_features *= 2\n        if cfg.use_multi_lat:\n            out_features *= 2\n        # \"cancer\", \"biopsy\", \"invasive\", \"age_scaled\", \"BIRADS_scaled\", \"machine_id_enc\", \"site_id\"\n        head = nn.Linear(out_features, 1, bias=True)\n        head_biopsy = nn.Linear(out_features, 1, bias=True)\n        head_invasive = nn.Linear(out_features, 1, bias=True)\n        head_birads = nn.Linear(out_features, 1, bias=True)\n        head_difficult_negative_case = nn.Linear(out_features, 1, bias=True)\n        head_age = nn.Linear(out_features, 1, bias=True)\n        head_machine_id = nn.Linear(out_features, 11, bias=True)\n        head_site_id = nn.Linear(out_features, 1, bias=True)\n        if cfg.use_multi_lat:\n            # LR model\n            head_2 = nn.Linear(out_features, 1, bias=True)\n            head_biopsy_2 = nn.Linear(out_features, 1, bias=True)\n            head_invasive_2 = nn.Linear(out_features, 1, bias=True)\n            head_birads_2 = nn.Linear(out_features, 1, bias=True)\n            head_difficult_negative_case_2 = nn.Linear(out_features, 1, bias=True)\n    else:\n        raise ValueError(f\"{cfg.head.type} is not implemented\")\n\n    head_all = nn.Sequential()\n    head_all.add_module(\"head\", head)\n    head_all.add_module(\"head_biopsy\", head_biopsy)\n    head_all.add_module(\"head_invasive\", head_invasive)\n    head_all.add_module(\"head_birads\", head_birads)\n    head_all.add_module(\"head_difficult_negative_case\", head_difficult_negative_case)\n    head_all.add_module(\"head_age\", head_age)\n    head_all.add_module(\"head_machine_id\", head_machine_id)\n    head_all.add_module(\"head_site_id\", head_site_id)\n    if cfg.use_multi_lat:\n        head_all.add_module(\"head_2\", head_2)\n        head_all.add_module(\"head_biopsy_2\", head_biopsy_2)\n        head_all.add_module(\"head_invasive_2\", head_invasive_2)\n        head_all.add_module(\"head_birads_2\", head_birads_2)\n        head_all.add_module(\"head_difficult_negative_case_2\", head_difficult_negative_case_2)\n    model.add_module(\"head\", head_all)\n\n    ckpt = torch.load(weight_path, map_location=\"cpu\")\n    model_dict = get_weights_to_load(model, ckpt)\n    model.load_state_dict(model_dict, strict=True)\n\n    return model\n\n\ndef init_backbone(cfg: DictConfig, pretrained: bool) -> BackboneBase:\n    in_chans = cfg.in_chans\n    backbone = load_backbone(\n        base_model=cfg.base_model,\n        pretrained=pretrained,\n        in_chans=in_chans,\n    )\n    if cfg.grad_checkpointing:\n        backbone.set_grad_checkpointing()\n    if cfg.freeze_backbone:\n        for param in backbone.parameters():\n            param.requires_grad = False\n    return backbone\n\n\nclass Forwarder(nn.Module):\n    def __init__(self, model: nn.Module, cfg: DictConfig) -> None:\n        super().__init__()\n        self.model = model\n        self.cfg = cfg\n\n    def forward(self, inputs: Tensor):\n        use_multi_view = self.cfg.forwarder.use_multi_view\n        use_multi_lat = self.cfg.forwarder.use_multi_lat\n        use_single_view = (not use_multi_view) and (not use_multi_lat)\n\n        # inputs: Input tensor.\n        # reshape for multiple image model\n        bs, ch, h, w = inputs.shape\n        if use_multi_view or use_multi_lat:\n            inputs = inputs.view(bs * ch, 1, h, w)\n\n        if use_single_view:\n            assert ch == 1\n        elif use_multi_lat:\n            assert ch == 4\n        elif use_multi_view:\n            assert ch == 2\n\n        # extract features\n        embed_features = self.model.forward_features(inputs)\n        if use_multi_view or use_multi_lat:\n            embed_features = embed_features.view(bs, -1)\n\n        # head\n        logits = self.model.head.head(embed_features)\n        if use_multi_lat:\n            logits_2 = self.model.head.head_2(embed_features)\n            return logits, logits_2\n\n        return logits\n    \n    \nclass RSNADataset(Dataset):\n    \n    ROOT_PATH = Path(\"/tmp/output/\")\n    \n    def __init__(\n        self,\n        df: pd.DataFrame,\n        cfg: DictConfig=None,\n    ) -> None:\n        root = self.ROOT_PATH\n        self.df = df.copy()\n        self.roi_th = cfg.dataset.roi_th\n        self.roi_buffer = cfg.dataset.roi_buffer\n        self.use_multi_view = cfg.dataset.use_multi_view\n        self.use_multi_lat = cfg.dataset.use_multi_lat\n        self.prediction_id_to_filename_dict = self.get_prediction_id_to_filename_map(self.df)\n        self.df[\"prediction_id\"] = self.df[\"patient_id\"].astype(str) + \"_\" + self.df[\"laterality\"]\n        self.df[\"filename\"] = self.df[\"patient_id\"].astype(str) + \"_\" + self.df[\"image_id\"].astype(str) + \".png\"\n        self.index = np.arange(len(self.df))\n        self.use_single_view = (not self.use_multi_view) and (not self.use_multi_lat)\n        if self.use_single_view:\n            self.index = np.arange(len(self.df))\n        elif self.use_multi_lat:\n            self.index = self.df[\"patient_id\"].unique()\n        elif self.use_multi_view:\n            self.index = self.df[\"prediction_id\"].unique()\n        \n        transforms = [\n            # Targets: image, mask, bboxes, keypoints\n            A.Resize(cfg.preprocessing.h_resize_to, cfg.preprocessing.w_resize_to, p=1),\n            # Targets: image\n            A.Normalize(mean=cfg.preprocessing.mean, std=cfg.preprocessing.std),\n            # Targets: image, mask\n            ToTensorV2(transpose_mask=True),\n        ]\n        self.transform = A.Compose(transforms)\n    \n    def __len__(self) -> int:\n        return len(self.index)\n\n    def get_prediction_id_to_filename_map(self, df):\n        prediction_id_dict = {}\n        for idx, row in df.iterrows():\n            patient_id = row[\"patient_id\"]\n            laterality = row[\"laterality\"]\n            prediction_id = f\"{patient_id}_{laterality}\"\n            image_id = row[\"image_id\"]\n            view = row[\"view\"]\n            if prediction_id not in prediction_id_dict:\n                prediction_id_dict[prediction_id] = defaultdict(list)\n            prediction_id_dict[prediction_id][view].append(f\"{patient_id}_{image_id}.png\")\n            \n        for prediction_id in prediction_id_dict:\n            for view in prediction_id_dict[prediction_id]:\n                prediction_id_dict[prediction_id][view] = prediction_id_dict[prediction_id][view][0]\n            \n        return prediction_id_dict\n    \n    def get_prediction_ids_and_image_paths(self, index):\n        root = self.ROOT_PATH\n        if self.use_single_view:\n            prediction_ids = [self.df[\"prediction_id\"].values[index]]\n            image_paths = [root / self.df[\"filename\"].values[index]]\n        elif self.use_multi_lat:\n            prediction_id_l = f\"{self.index[index]}_L\"\n            prediction_id_r = f\"{self.index[index]}_R\"\n            prediction_ids = [prediction_id_l, prediction_id_r]\n            filename_cc_l = self.prediction_id_to_filename_dict[prediction_id_l][\"CC\"]\n            filename_mlo_l = self.prediction_id_to_filename_dict[prediction_id_l][\"MLO\"]\n            filename_cc_r = self.prediction_id_to_filename_dict[prediction_id_r][\"CC\"]\n            filename_mlo_r = self.prediction_id_to_filename_dict[prediction_id_r][\"MLO\"]\n            image_paths = [root / filename_cc_l, root / filename_mlo_l, root / filename_cc_r, root / filename_mlo_r]\n        elif self.use_multi_view:\n            prediction_id = self.index[index]\n            prediction_ids = [prediction_id]\n            filename_cc = self.prediction_id_to_filename_dict[prediction_id][\"CC\"]\n            filename_mlo = self.prediction_id_to_filename_dict[prediction_id][\"MLO\"]\n            image_paths = [root / filename_cc, root / filename_mlo]\n        return prediction_ids, image_paths\n    \n    def read_image(self, image_path):\n        image = cv2.imread(str(image_path))\n        image = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)\n        return image\n    \n    def get_roi_crop(self, image, threshold=0.1, buffer=30):\n        y_max, x_max = image.shape\n        image2 = image > image.mean()\n        y_mean = image2.mean(1)\n        x_mean = image2.mean(0)\n        x_mean[:5] = 0\n        x_mean[-5:] = 0\n        y_mean[:5] = 0\n        y_mean[-5:] = 0\n        y_mean = (y_mean - y_mean.min() + 1e-4) / (y_mean.max() - y_mean.min() + 1e-4)\n        x_mean = (x_mean - x_mean.min() + 1e-4) / (x_mean.max() - x_mean.min() + 1e-4)\n        y_slice = np.where(y_mean > threshold)[0]\n        x_slice = np.where(x_mean > threshold)[0]\n        if len(x_slice) == 0:\n            x_start, x_end = 0, x_max\n        else:\n            x_start, x_end = max(x_slice.min() - buffer, 0), min(\n                x_slice.max() + buffer, x_max\n            )\n        if len(y_slice) == 0:\n            y_start, y_end = 0, y_max\n        else:\n            y_start, y_end = max(y_slice.min() - buffer, 0), min(\n                y_slice.max() + buffer, y_max\n            )\n        return x_start, y_start, x_end, y_end\n    \n    def get_bbox(self, image):\n        th = self.roi_th\n        buffer = self.roi_buffer\n        x_min, y_min, x_max, y_max = self.get_roi_crop(\n            image, threshold=th, buffer=buffer\n        )\n        return x_min, y_min, x_max, y_max\n    \n    def crop_image(self, image):\n        x_min, y_min, x_max, y_max = self.get_bbox(image)\n        return image[y_min:y_max, x_min:x_max]\n    \n    def apply_transform(self, image):\n        return self.transform(image=image)[\"image\"]\n\n    def __getitem__(self, index: int):\n        prediction_ids, image_paths = self.get_prediction_ids_and_image_paths(index)\n        images = [self.read_image(image_path) for image_path in image_paths]\n        images = [self.crop_image(image) for image in images]\n        images = torch.concat([self.apply_transform(image) for image in images])\n        return images, prediction_ids","metadata":{"_kg_hide-input":true,"_kg_hide-output":true,"execution":{"iopub.status.busy":"2023-02-27T01:05:27.388253Z","iopub.execute_input":"2023-02-27T01:05:27.38866Z","iopub.status.idle":"2023-02-27T01:05:58.428584Z","shell.execute_reply.started":"2023-02-27T01:05:27.388615Z","shell.execute_reply":"2023-02-27T01:05:58.427408Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# Load experiment","metadata":{"execution":{"iopub.status.busy":"2023-01-19T05:04:04.618002Z","iopub.execute_input":"2023-01-19T05:04:04.620294Z","iopub.status.idle":"2023-01-19T05:04:05.810977Z","shell.execute_reply.started":"2023-01-19T05:04:04.620255Z","shell.execute_reply":"2023-01-19T05:04:05.809801Z"}}},{"cell_type":"code","source":"train = pd.read_csv('../input/rsna-breast-cancer-detection/train.csv')\ntest = pd.read_csv('../input/rsna-breast-cancer-detection/test.csv')\nif DEBUG:\n    test = train.loc[\n        train['patient_id'].isin(SAMPLE_ID), \n        [\n            'site_id', 'patient_id', 'image_id', 'laterality', \n            'view', 'age', 'implant', 'machine_id', 'cancer'\n        ]]\nelse:\n    test['cancer'] = 0","metadata":{"execution":{"iopub.status.busy":"2023-02-27T01:05:58.432389Z","iopub.execute_input":"2023-02-27T01:05:58.433739Z","iopub.status.idle":"2023-02-27T01:05:58.545022Z","shell.execute_reply.started":"2023-02-27T01:05:58.433692Z","shell.execute_reply":"2023-02-27T01:05:58.544013Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# Interface","metadata":{}},{"cell_type":"code","source":"def get_threshold(prec_oof, predictions, test_sites, mode, threshold_params):\n    folds = list(range(4))\n    if mode == 'OOF': # concat oofs and search a new threshold (soft vote)\n        oof_labels, oof_preds = prec_oof['oof_labels'], prec_oof['oof_preds']\n        score, threshold = Pfbeta().optimal_f1(oof_labels, oof_preds)\n        print(score, threshold)\n        predictions = predictions.mean(0)\n        # predictions = (predictions > threshold).astype(float)\n        thresholds = [threshold, threshold]\n\n    elif mode == 'PROPORTIONAL':\n        predictions = predictions.mean(0)\n        oof_labels, oof_preds, oof_folds = prec_oof['oof_labels'], prec_oof['oof_preds'], prec_oof['oof_folds']\n        metric = PercentilePfbeta(percentile_range=[97.5, 99], n_trials=30)\n        score, percentile, threshold = metric(oof_preds, oof_labels)\n        print(score, percentile, threshold)\n        threshold = np.percentile(predictions, percentile)\n        print(threshold)\n        # predictions = (predictions > threshold).astype(float)\n        thresholds = [threshold, threshold]\n\n    elif mode == 'PROPORTIONAL_PER_SITE':\n        test_sites = np.array(test_sites)\n        predictions = predictions.mean(0)\n        oof_labels, oof_preds, oof_sites, oof_folds = \\\n            prec_oof['oof_labels'], prec_oof['oof_preds'],  prec_oof['oof_sites'], prec_oof['oof_folds'] \n        metric = PercentilePfbeta(percentile_range=[97.5, 99], n_trials=30)\n        score, percentile, threshold = metric(oof_preds, oof_labels)\n        score_s1, percentile_s1, threshold_s1 = metric(oof_preds[oof_sites==1], oof_labels[oof_sites==1])\n        score_s2, percentile_s2, threshold_s2 = metric(oof_preds[oof_sites==2], oof_labels[oof_sites==2])\n        print(score_s1, percentile_s1, threshold_s1)\n        print(score_s2, percentile_s2, threshold_s2)\n        threshold_s1 = np.percentile(predictions[test_sites == 1], percentile_s1)\n        threshold_s2 = np.percentile(predictions[test_sites == 2], percentile_s2)\n        print(threshold_s1, threshold_s2)\n        # predictions[test_sites == 1] = (predictions[test_sites == 1] > threshold_s1).astype(float)\n        # predictions[test_sites == 2] = (predictions[test_sites == 2] > threshold_s2).astype(float)\n        thresholds = [threshold_s1, threshold_s2]\n\n    elif mode == 'PROPORTIONAL_MEAN':\n        predictions = predictions.mean(0)\n        oof_labels, oof_preds, oof_folds = prec_oof['oof_labels'], prec_oof['oof_preds'], prec_oof['oof_folds']\n        metric = PercentilePfbeta(percentile_range=[97.5, 99], n_trials=30)\n        percentiles = []\n        for fold in folds:\n            score_f, percentile_f, threshold_f = metric(oof_preds[oof_folds==fold], oof_labels[oof_folds==fold])\n            print(score_f, percentile_f, threshold_f)\n            percentiles.append(percentile_f)\n        mean_percentile = np.mean(percentiles)\n        threshold = np.percentile(predictions, mean_percentile)\n        print(mean_percentile, threshold)\n        # predictions = (predictions > threshold).astype(float)\n        thresholds = [threshold, threshold]\n\n    elif mode == 'PROPORTIONAL_MEAN_PER_SITE':\n        test_sites = np.array(test_sites)\n        predictions = predictions.mean(0)\n        oof_labels, oof_preds, oof_sites, oof_folds = \\\n            prec_oof['oof_labels'], prec_oof['oof_preds'],  prec_oof['oof_sites'], prec_oof['oof_folds'] \n        metric = PercentilePfbeta(percentile_range=[97.5, 99], n_trials=30)\n        thresholds = []\n        for site in [1, 2]:\n            oof_preds_fold = oof_preds[oof_sites == site]\n            oof_labels_fold = oof_labels[oof_sites == site]\n            oof_folds_fold = oof_folds[oof_sites == site]\n            percentiles = []\n            for fold in folds:\n                score_f, percentile_f, threshold_f = metric(\n                    oof_preds_fold[oof_folds_fold==fold], oof_labels_fold[oof_folds_fold==fold])\n                print(score_f, percentile_f, threshold_f)\n                percentiles.append(percentile_f)\n            mean_percentile = np.mean(percentiles)\n            threshold = np.percentile(predictions[test_sites == site], mean_percentile)\n            print(site, mean_percentile, threshold)\n            # predictions[test_sites == site] = (predictions[test_sites == site] > threshold).astype(float)\n            thresholds.append(threshold)\n\n    elif mode == 'PROPORTIONAL_MEAN_PER_SITE_OFFSET':\n        '''\n        Assumptions: \n        all rejected samples have value smaller than the current inference set.\n\n        for fold:\n            percentile_fold = pf1(oof.fold, label.fold)\n            offset = 100 * len(infer_df) / len(total_df)\n            percentile_adjusted = (percentile_fold - offset) / (100 - offset)\n        '''\n        test_sites = np.array(test_sites)\n        predictions = predictions.mean(0)\n        offset = 100 * (1 - len(predictions) / threshold_params['total_size'])\n        oof_labels, oof_preds, oof_sites, oof_folds = \\\n            prec_oof['oof_labels'], prec_oof['oof_preds'],  prec_oof['oof_sites'], prec_oof['oof_folds'] \n        metric = PercentilePfbeta(percentile_range=[97.5, 99], n_trials=30)\n        thresholds = []\n        for site in [1, 2]:\n            oof_preds_fold = oof_preds[oof_sites == site]\n            oof_labels_fold = oof_labels[oof_sites == site]\n            oof_folds_fold = oof_folds[oof_sites == site]\n            percentiles = []\n            for fold in folds:\n                score_f, percentile_f, threshold_f = metric(\n                    oof_preds_fold[oof_folds_fold==fold], oof_labels_fold[oof_folds_fold==fold])\n                print(score_f, percentile_f, threshold_f)\n                percentiles.append(percentile_f)\n            mean_percentile = np.mean(percentiles)\n            mean_percentile = 100 * (mean_percentile - offset) / (100 - offset)\n            print(f'1 - offset = {len(predictions)}/{threshold_params[\"total_size\"]}')\n            threshold = np.percentile(predictions[test_sites == site], mean_percentile)\n            print(site, mean_percentile, threshold)\n            # predictions[test_sites == site] = (predictions[test_sites == site] > threshold).astype(float)\n            thresholds.append(threshold)\n\n    elif mode == 'OOF_PER_SITE':\n        test_sites = np.array(test_sites)\n        predictions = predictions.mean(0)\n        oof_labels, oof_preds, oof_sites = prec_oof['oof_labels'], prec_oof['oof_preds'], prec_oof['oof_sites']\n        score_s1, threshold_s1 = Pfbeta().optimal_f1(oof_labels[oof_sites==1], oof_preds[oof_sites==1])\n        score_s2, threshold_s2 = Pfbeta().optimal_f1(oof_labels[oof_sites==2], oof_preds[oof_sites==2])\n        print(score_s1, threshold_s1)\n        print(score_s2, threshold_s2)\n        # predictions[test_sites == 1] = (predictions[test_sites == 1] > threshold_s1).astype(float)\n        # predictions[test_sites == 2] = (predictions[test_sites == 2] > threshold_s2).astype(float)\n        thresholds = [threshold_s1, threshold_s2]\n\n    elif mode == 'OOF_MEAN_PER_SITE':\n        test_sites = np.array(test_sites)\n        predictions = predictions.mean(0)\n        oof_labels, oof_preds, oof_sites, oof_folds = \\\n            prec_oof['oof_labels'], prec_oof['oof_preds'], prec_oof['oof_sites'], prec_oof['oof_folds']\n        metric = PercentilePfbeta(percentile_range=[97.5, 99], n_trials=30)\n        thresholds = []\n        for site in [1, 2]:\n            oof_preds_site = oof_preds[oof_sites == site]\n            oof_labels_site = oof_labels[oof_sites == site]\n            oof_folds_site = oof_folds[oof_sites == site]\n            thresholds_site = []\n            for fold in folds:\n                score_f, percentile_f, threshold_f = metric(\n                    oof_preds_site[oof_folds_site==fold], oof_labels_site[oof_folds_site==fold])\n                print(fold, score_f, percentile_f, threshold_f)\n                thresholds_site.append(threshold_f)\n            mean_threshold = np.mean(thresholds_site)\n            print(site, mean_threshold)\n            # predictions[test_sites == site] = (predictions[test_sites == site] > mean_threshold).astype(float)\n            thresholds.append(mean_threshold)\n            \n    elif mode == 'OOF_MEAN_PER_SITE_EASY':\n        test_sites = np.array(test_sites)\n        predictions = predictions.mean(0)\n        oof_labels, oof_preds, oof_sites, oof_folds = \\\n            prec_oof['oof_labels'], prec_oof['oof_preds'], prec_oof['oof_sites'], prec_oof['oof_folds']\n        oof_hardflags = prec_oof['oof_hardflags']\n        oof_preds = oof_preds[oof_hardflags == 0]\n        oof_labels = oof_labels[oof_hardflags == 0]\n        oof_folds = oof_folds[oof_hardflags == 0]\n        oof_sites = oof_sites[oof_hardflags == 0]\n        print(f'{oof_hardflags.sum()} hard samples excluded from oof.')\n        metric = PercentilePfbeta(percentile_range=[97.5, 99], n_trials=30)\n        thresholds = []\n        for site in [1, 2]:\n            oof_preds_site = oof_preds[oof_sites == site]\n            oof_labels_site = oof_labels[oof_sites == site]\n            oof_folds_site = oof_folds[oof_sites == site]\n            thresholds_site = []\n            for fold in folds:\n                score_f, percentile_f, threshold_f = metric(\n                    oof_preds_site[oof_folds_site==fold], oof_labels_site[oof_folds_site==fold])\n                print(fold, score_f, percentile_f, threshold_f)\n                thresholds_site.append(threshold_f)\n            mean_threshold = np.mean(thresholds_site)\n            print(site, mean_threshold)\n            # predictions[test_sites == site] = (predictions[test_sites == site] > mean_threshold).astype(float)\n            thresholds.append(mean_threshold)\n\n    return thresholds","metadata":{"_kg_hide-input":true,"execution":{"iopub.status.busy":"2023-02-27T01:05:58.548642Z","iopub.execute_input":"2023-02-27T01:05:58.548943Z","iopub.status.idle":"2023-02-27T01:05:58.583344Z","shell.execute_reply.started":"2023-02-27T01:05:58.548916Z","shell.execute_reply":"2023-02-27T01:05:58.58234Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"class KumaInference:\n    \n    def get_oof_path(self, cfg):\n        return Path('../input/rsna-oof/kuma_oofs_v3/')/f'kuma_{cfg.name}.csv'\n    \n    def get_dataloader(\n        self, \n        cfg, \n        test_df, \n        results_dir=Path(f'../input/rsna-mammo-2023/dataset/results/'),\n        batch_size_ratio=1):\n        '''\n        input parameters:\n            cfg: kuma_utils config class\n            test: test dataframe to predict\n            results_dir: pathlib.Path results directory\n            batch_size_ratio: bs = bs // ratio\n\n        output parameters:\n            test_loader: pytorch dataloader\n            test_indices: list of indices corresponding to test_loader\n                e.g. [(57, L), (57, R), (10007, L), ..., ]\n        '''\n        proj_dir = results_dir / cfg.name\n        cfg.dataset_params['sep'] = '_'\n        cfg.dataset_params['aux_target_cols'] = []\n        if 'bbox_path' in cfg.dataset_params and cfg.dataset_params['bbox_path'] is not None:\n            pd.DataFrame([{'name': 'dummy', 'xmin': 0, 'ymin': 0, 'xmax':0, 'ymax':0}]).to_csv(\n                'dummy_bbox.csv', index=False)\n            cfg.dataset_params['bbox_path'] = 'dummy_bbox.csv'\n        else:\n            cfg.dataset_params['bbox_path'] = None\n        cfg.model_params['pretrained'] = False\n        test_data = cfg.dataset(\n            df=test_df, \n            image_dir=EXPORT_DIR,\n            preprocess=cfg.preprocess['test'],\n            transforms=cfg.transforms['test'],\n            is_test=True,\n            **cfg.dataset_params)\n        test_loader = D.DataLoader(\n            test_data, batch_size=cfg.batch_size//batch_size_ratio, shuffle=False,\n            num_workers=2, pin_memory=False)\n\n        if test_data.__class__.__name__ == 'PatientLevelDatasetLR':\n            test_indices = []\n            for pid in test_data.pids:\n                test_indices.extend([(pid, 'L'), (pid, 'R')])\n        else:\n            test_indices = test_data.pids\n\n        test_sites = [test_data.df_dict[test_data.pids[i]]['site_id'].values[0] for i in range(len(test_data))]\n        return test_loader, test_indices, test_sites\n    \n    def infer_model(\n        self, \n        cfg, \n        test_loader, \n        folds=None, \n        results_dir=Path('../input/rsna-mammo-2023/dataset/results/'),\n        fp16=False):\n        '''\n        input parameters:\n            cfg: kuma_utils config class\n            test_loader: test dataloader\n            folds: list of folds to include\n            results_dir: pathlib.Path results directory\n\n        output parameters:\n            predictions: np.array [cv, test samples, 1]\n        '''\n        def _load_data(input_t):\n            if isinstance(input_t, torch.Tensor):\n                x = input_t\n            elif isinstance(input_t, (list, tuple)):\n                x = input_t[0]\n            return x\n\n        def _predict(model, x):\n            with torch.no_grad():\n                if fp16:\n                    with amp.autocast():\n                        y = model(x)\n                else:\n                    y = model(x)\n            if isinstance(y, (list, tuple)):\n                y = y[0]\n            if len(y.shape) > 1 and y.shape[1] > 1:\n                y = y[:, 0]\n            return y.float().cpu().numpy()\n\n        proj_dir = results_dir / cfg.name\n        predictions = []\n        if folds is None:\n            folds = list(range(cfg.cv))\n        models = []\n        for fold in range(cfg.cv):\n            if not fold in folds:\n                continue\n            model = cfg.model(**cfg.model_params)\n            if DEBUG:\n                checkpoint = torch.load(proj_dir/f'fold{DEBUG_FOLD}.pt', 'cpu')\n                print(str(proj_dir/f'fold{DEBUG_FOLD}.pt'))\n            else:\n                checkpoint = torch.load(proj_dir/f'fold{fold}.pt', 'cpu')\n                print(str(proj_dir/f'fold{fold}.pt'))\n            model.load_state_dict(checkpoint['model'])\n            if cfg.parallel == 'ddp':\n                model = convert_sync_batchnorm(model)\n            model.cuda()\n            model.eval()\n            models.append(model)\n            del checkpoint; gc.collect()\n        predictions = []\n        for input_t in tqdm(test_loader):\n            x = _load_data(input_t).cuda()\n            ys = []\n            for m in models:\n                ys.append(_predict(m, x))\n            ys = np.stack(ys, axis=0) # (cv, bs, 1)\n            predictions.append(ys)\n        predictions = np.concatenate(predictions, axis=1)\n        del models; gc.collect()\n        torch.cuda.empty_cache()\n        return predictions\n    \n    def apply_threshold(\n        self,\n        cfg, \n        raw_predictions, \n        test_sites, \n        mode='OOF',\n        threshold_params={}):\n        '''\n        input params:\n            cfg: kuma_utils config class\n            predictions: np.array [cv, test samples, 1]\n            mode: str \n            folds: list of folds to include\n            results_dir: pathlib.Path results directory\n            use_extended_oof: False\n\n        output params:\n            predictions: np.array [test_samples, ]\n            or thresholds: list [threshold for site1, threshold for site2]\n        '''\n        def load_precalculated_oof(path):\n            oof = pd.read_csv(path)[['prediction_id', 'pred', 'site_id', 'fold']].merge(\n                train[['prediction_id', 'target', 'difficult_negative_case']], on='prediction_id', how='left')\n            return {\n                'oof_labels': oof['target'].values,\n                'oof_preds': oof['pred'].values,\n                'oof_sites': oof['site_id'].values,\n                'oof_folds': oof['fold'].values,\n                'oof_hardflags': oof['difficult_negative_case'].astype(int).values\n            }\n\n        predictions = raw_predictions.copy()\n        folds = list(range(4))\n        precalculated_path = Path('../input/rsna-oof/kuma_oofs_v3/')/f'kuma_{cfg.name}.csv'\n        print(precalculated_path)\n        train = pd.read_csv('../input/rsna-misc/train_with_fold.csv').rename({'cancer': 'target'}, axis=1)\n        train['prediction_id'] = train['patient_id'].astype(str) + '_' + train['laterality']\n        train = train.groupby('prediction_id').agg({'target': 'max', 'fold': 'max', 'difficult_negative_case': 'max'}).reset_index()\n        prec_oof = load_precalculated_oof(precalculated_path)\n        thresholds = get_threshold(prec_oof, predictions, test_sites, mode, threshold_params)\n        return thresholds\n    \n    def extend_df(self, df, max_record_per_patient=2, view_category=[['MLO', 'LMO', 'LM', 'ML'], ['CC', 'AT']]):\n        '''\n        Extend the train df to include multiple images\n        '''\n        def _sample_idx(df, used_ids=[], sample_all=False):\n            new_pdf = []\n            for iv, view_cat in enumerate(view_category):\n                view0 = pdf.loc[pdf['view'].isin(view_cat) & ~pdf['image_id'].isin(used_ids)]\n                if len(view0) == 0:\n                    new_pdf.append(pdf.loc[pdf['view'].isin(view_cat)])\n                elif sample_all:\n                    new_pdf.append(view0)\n                else:\n                    new_pdf.append(view0.sample(min(len(view0), max(1, len(view0)//max_record_per_patient))))\n            return pd.concat(new_pdf).reset_index(drop=True)\n\n        new_df = []\n        for plr, pdf in df.groupby(['patient_id', 'laterality']):\n            if len(pdf) == 2:\n                pdf['oversample_id'] = 0\n                new_df.append(pdf)\n            else:\n                used_ids = []\n                for i in range(max_record_per_patient):\n                    idf = _sample_idx(pdf, used_ids, sample_all=i == max_record_per_patient-1)\n                    idf['oversample_id'] = i\n                    new_df.append(idf)\n                    used_ids.extend(idf['image_id'].values.tolist())\n        return pd.concat(new_df).reset_index(drop=True)\n    \n    def aggregate_prediction(self, predictions, test_indices, test_sites, agg_func='mean'):\n        '''\n        mean the LOGITS (not PROB)\n        '''\n        agg_predictions = []\n        for pred_id, site, pred in zip(test_indices, test_sites, predictions.transpose(1, 0, 2)):\n            record = {\n                'prediction_id': f'{pred_id[1]}_{pred_id[2]}',\n                'site_id': site,\n                'pred0': pred[0][0], 'pred1': pred[1][0], 'pred2': pred[2][0], 'pred3': pred[3][0]\n            }\n            agg_predictions.append(record)\n        agg_predictions = pd.DataFrame(agg_predictions)\n        agg_predictions = agg_predictions.groupby('prediction_id').agg({\n            'pred0': agg_func, 'pred1': agg_func, 'pred2': agg_func, 'pred3': agg_func, \n            'site_id': 'max'}).reset_index()\n        return (\n            agg_predictions[['pred0', 'pred1', 'pred2', 'pred3']].values.transpose(1, 0).reshape(4, -1, 1),\n            agg_predictions['prediction_id'].values.tolist(),\n            agg_predictions['site_id'].values.tolist())\n    \n    def inference_block(self, cfg, test_df, threshold_mode, batch_size_ratio=1, fp16=False, extend=0, threshold_params={}):\n        if extend > 0:\n            test_df = self.extend_df(test_df, max(2, 1+extend))\n        test_loader, test_indices, test_sites = self.get_dataloader(cfg, test_df=test_df, batch_size_ratio=batch_size_ratio)\n        predictions = self.infer_model(cfg, test_loader, fp16=fp16)\n        if extend > 0:\n            predictions, prediction_ids, test_sites = self.aggregate_prediction(\n                predictions, test_indices, test_sites)\n        else:\n            prediction_ids = [f'{pid}_{lat}' for pid, lat in test_indices]\n        predictions = sigmoid(predictions)\n        thresholds = self.apply_threshold(\n            cfg, predictions, test_sites, mode=threshold_mode, threshold_params=threshold_params)\n        results = []\n        for pred_id, pred, site in zip(prediction_ids, predictions.mean(0), test_sites):\n            results.append({\n                'prediction_id': pred_id,\n                cfg.name: pred[0],\n                'site_id': site\n            })\n        results = pd.DataFrame(results)\n        return results, thresholds","metadata":{"_kg_hide-input":true,"execution":{"iopub.status.busy":"2023-02-27T01:05:58.585099Z","iopub.execute_input":"2023-02-27T01:05:58.585569Z","iopub.status.idle":"2023-02-27T01:05:58.624928Z","shell.execute_reply.started":"2023-02-27T01:05:58.585533Z","shell.execute_reply":"2023-02-27T01:05:58.623892Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"class AriyasuInference:\n    \n    def get_oof_path(self, cfg):\n        return list(Path('../input/rsna-oof/ariyasu/').glob(f'{cfg.file_name}*.csv'))[0]\n\n    def get_models(self, cfg):\n        device = torch.device(\"cuda:0\" if torch.cuda.is_available() else \"cpu\")\n        models = []\n        for model_name, model_path in zip(cfg.model_names, cfg.model_paths):\n            print(model_path)\n            if 'nextvit' in model_name:\n                model = NextVitNet(model_name, pretrained=False, num_classes=cfg.num_classes)\n            else:\n                model = timm.create_model(model_name, pretrained=False, num_classes=cfg.num_classes)\n            state_dict = torch.load(model_path, map_location=torch.device('cpu'))['state_dict']\n            torch_state_dict = {}\n            for k, v in state_dict.items():\n                torch_state_dict[k[6:]] = v\n            model.load_state_dict(torch_state_dict)\n            model.to(device)\n            model.eval()\n            # if 'nano' in model_name:\n            #     model = torch_tensorrt.compile(model, inputs = [torch_tensorrt.Input(min_shape=[1, 3, image_size[1], image_size[0]],opt_shape=[cfg.batch_size, 3, image_size[1], image_size[0]],max_shape=[cfg.batch_size, 3, image_size[1], image_size[0]],dtype=torch.float32)],\n            #         enabled_precisions = torch.float32, # Run with FP32\n            #         workspace_size = 1 << 32\n            #     )\n            models.append(model)\n        del model\n        torch.cuda.empty_cache()\n        return models\n\n    def apply_threshold(\n        self,\n        cfg,\n        raw_predictions,\n        test_sites,\n        mode='OOF',\n        threshold_params={}):\n        def load_precalculated_oof(path):\n            oof = pd.read_csv(path)[['prediction_id', 'pred', 'site_id', 'fold']].merge(\n                train[['prediction_id', 'target', 'difficult_negative_case']], on='prediction_id', how='left')\n            return {\n                'oof_labels': oof['target'].values,\n                'oof_preds': oof['pred'].values,\n                'oof_sites': oof['site_id'].values,\n                'oof_folds': oof['fold'].values,\n                'oof_hardflags': oof['difficult_negative_case'].astype(int).values\n            }\n\n        predictions = raw_predictions.copy()\n        folds = list(range(4))\n        precalculated_path = list(Path('../input/rsna-oof/ariyasu/').glob(f'{cfg.file_name}*.csv'))\n        print(precalculated_path)\n        precalculated_path = precalculated_path[0]\n        train = pd.read_csv('../input/rsna-misc/train_with_fold.csv').rename({'cancer': 'target'}, axis=1)\n        train['prediction_id'] = train['patient_id'].astype(str) + '_' + train['laterality']\n        train = train.groupby('prediction_id').agg({'target': 'max', 'fold': 'max', 'difficult_negative_case': 'max'}).reset_index()\n        prec_oof = load_precalculated_oof(precalculated_path)\n        thresholds = get_threshold(prec_oof, predictions, test_sites, mode, threshold_params)\n        return thresholds  \n    \n    def inference_block(self, cfg, test, threshold_mode, threshold_params={}, fp16=False):\n        assert cfg.tta == 1\n        device = torch.device(\"cuda:0\" if torch.cuda.is_available() else \"cpu\")\n\n        models = self.get_models(cfg)\n\n        print('len(test):', len(test))\n        print('tta:', cfg.tta)\n        ds = RSNADatasetAriyasu(test.path.values, cfg)\n        loader = DataLoader(ds, batch_size=cfg.batch_size, shuffle=False, drop_last=False, num_workers=2)\n        preds = []\n        with torch.no_grad():\n            for images in tqdm(loader, smoothing=0):\n                images = images.to(device)\n#                 batch_preds = []\n                ys = []\n                for model in models:\n                    if fp16:\n                        with amp.autocast():\n                            logits = model(images)\n                    else:\n                        logits = model(images)\n\n                    ys.append(logits.sigmoid().cpu().detach().numpy())\n                ys = np.stack(ys) # (cv, bs, 1)\n                preds.append(ys)\n        preds = np.concatenate(preds, axis=1)\n                \n        del models; torch.cuda.empty_cache()\n\n        test[[f'pred_fold_{fold}' for fold in range(4)]] = preds[:,:,0].T\n        for fold in range(4):\n            df = pd.DataFrame(test.groupby('prediction_id')[f'pred_fold_{fold}'].mean()).reset_index()\n            del test[f'pred_fold_{fold}']\n            test = test.merge(df, on='prediction_id')\n        df = test.drop_duplicates('prediction_id')\n        predictions = df[[f'pred_fold_{fold}' for fold in range(4)]].values.T\n        predictions = predictions[:,:,np.newaxis]\n        _sites = df.site_id.values\n        thresholds = self.apply_threshold(\n            cfg, predictions, _sites, mode=threshold_mode, threshold_params=threshold_params)\n\n        df[cfg.file_name] = df[[f'pred_fold_{fold}' for fold in range(4)]].mean(1)\n\n        for col in [f'pred_fold_{fold}' for fold in range(4)]:\n            del test[col]\n        return df[['prediction_id', 'site_id', cfg.file_name]], thresholds","metadata":{"_kg_hide-input":true,"execution":{"iopub.status.busy":"2023-02-27T01:05:58.626319Z","iopub.execute_input":"2023-02-27T01:05:58.626808Z","iopub.status.idle":"2023-02-27T01:05:58.649781Z","shell.execute_reply.started":"2023-02-27T01:05:58.626773Z","shell.execute_reply":"2023-02-27T01:05:58.648668Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"class IshikeiInference:\n\n    def _init_cfg(self, cfg):\n        cfg.dataset_params['sep'] = '_'\n        cfg.dataset_params['aux_target_cols'] = []\n        cfg.dataset_params['bbox_path'] = None\n        cfg.model_params['pretrained'] = False\n        if 'with_cp' in cfg.model_params:\n            cfg.model_params['with_cp'] = False\n        return cfg\n    \n    def get_oof_path(self, cfg):\n        return list(Path('../input/rsna-oof/ishikei_002/').glob(f'{cfg.name}*.csv'))[0]\n\n    def get_dataloader(self, cfg, test_df, batch_size=None):\n        if batch_size is None:\n            batch_size = cfg.batch_size\n        \n        # local - kaggle notebook diff\n        cfg = self._init_cfg(cfg)\n        \n        test_data = cfg.dataset(\n            df=test_df,\n            image_dir=EXPORT_DIR,\n            preprocess=cfg.preprocess['test'],\n            transforms=cfg.transforms['test'],\n            is_test=True,\n            **cfg.dataset_params)\n\n        test_loader = D.DataLoader(\n            test_data, batch_size=batch_size, shuffle=False,\n            num_workers=2, pin_memory=False)\n    \n        test_indices = test_data.pids\n        test_sites = [\n            test_data.df_dict[test_data.pids[i]]['site_id'].values[0] for i in range(len(test_data))\n        ]\n    \n        return test_loader, test_data, test_indices, test_sites\n    \n    def infer_model(self, cfg, test_loader, test_data, batch_size=None, fp16=False):\n        if batch_size is None:\n            batch_size = cfg.batch_size\n\n        proj_dir = Path(f'../input/ishikei-mammo/{cfg.name}')\n        folds = list(range(cfg.cv))\n        \n        # get models.\n        models = []\n        for fold in folds:\n            # init model.\n            model = cfg.model(**cfg.model_params)\n\n            if DEBUG:\n                print(f\"load: {str(proj_dir/f'fold{DEBUG_FOLD}.pt')}\")\n                checkpoint = torch.load(proj_dir/f'fold{DEBUG_FOLD}.pt', 'cpu')\n            else:\n                print(f\"load: {str(proj_dir/f'fold{fold}.pt')}\")\n                checkpoint = torch.load(proj_dir/f'fold{fold}.pt', 'cpu')\n            model.load_state_dict(checkpoint['model'])\n            del checkpoint; gc.collect()\n            \n            if cfg.parallel == 'ddp':\n                model = convert_sync_batchnorm(model)\n            model.to('cuda:0')\n            model.eval()\n            models.append(model)\n        \n        # inference.\n        predictions = []\n        with torch.no_grad():\n            for idx, data in tqdm(enumerate(test_loader), total=len(test_loader)):\n                data = [d.to('cuda:0') for d in data]\n                ys = []\n                for model in models:\n                    if fp16:\n                        with amp.autocast():\n                            logits = model(*data[:-1])\n                    else:\n                        logits = model(*data[:-1])\n                    ys.append(logits.sigmoid().cpu().detach().numpy())\n                ys = np.stack(ys) # (cv, bs, 1)\n                predictions.append(ys)\n        predictions = np.concatenate(predictions, axis=1)\n        \n        del models\n        gc.collect(); torch.cuda.empty_cache()\n        return predictions\n    \n    def apply_threshold(\n        self,\n        cfg, \n        raw_predictions, \n        test_sites,\n        mode='OOF',\n        threshold_params={},):\n        def load_precalculated_oof(path):\n            oof = pd.read_csv(path)[['prediction_id', 'pred', 'site_id', 'fold']].merge(\n                train[['prediction_id', 'target', 'difficult_negative_case']], on='prediction_id', how='left')\n            return {\n                'oof_labels': oof['target'].values,\n                'oof_preds': oof['pred'].values,\n                'oof_sites': oof['site_id'].values,\n                'oof_folds': oof['fold'].values,\n                'oof_hardflags': oof['difficult_negative_case'].astype(int).values\n            }\n\n        predictions = raw_predictions.copy()\n        folds = list(range(cfg.cv))\n        precalculated_path = list(Path('../input/rsna-oof/ishikei_002/').glob(f'{cfg.name}*.csv'))\n        print(precalculated_path)\n        precalculated_path = precalculated_path[0]\n        train = pd.read_csv('../input/rsna-misc/train_with_fold.csv').rename({'cancer': 'target'}, axis=1)\n        train['prediction_id'] = train['patient_id'].astype(str) + '_' + train['laterality']\n        train = train.groupby('prediction_id').agg({'target': 'max', 'fold': 'max', 'difficult_negative_case': 'max'}).reset_index()\n        prec_oof = load_precalculated_oof(precalculated_path)\n        thresholds = get_threshold(prec_oof, predictions, test_sites, mode, threshold_params)\n        return thresholds\n    \n    def inference_block(self, cfg, test_df, threshold_mode, batch_size=None, fp16=False, threshold_params={}):\n        '''\n        input parameters:\n            cfg: kuma_utils config class\n            test_df: test dataframe to inference\n            batch_size: inference batch_size\n\n        output parameters:\n            results: pd.DataFrame\n        '''\n        _loader, _dataset, _indices, _sites = self.get_dataloader(cfg, test_df, batch_size)\n        predictions = self.infer_model(cfg, _loader, _dataset, batch_size, fp16=fp16)\n        thresholds = self.apply_threshold(\n            cfg, predictions, _sites, mode=threshold_mode, threshold_params=threshold_params)\n        \n        results = []\n        for pred_id, site, pred in zip(_indices, _sites, predictions.mean(0)):\n            results.append({'prediction_id': f'{pred_id[0]}_{pred_id[1]}',\n                            'site_id': site,\n                            cfg.name: pred[0]})\n        results = pd.DataFrame(results)\n        return results, thresholds","metadata":{"_kg_hide-input":true,"_kg_hide-output":true,"execution":{"iopub.status.busy":"2023-02-27T01:05:58.651552Z","iopub.execute_input":"2023-02-27T01:05:58.651984Z","iopub.status.idle":"2023-02-27T01:05:58.677557Z","shell.execute_reply.started":"2023-02-27T01:05:58.651948Z","shell.execute_reply":"2023-02-27T01:05:58.676374Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"class CharmInterface:\n    \n    def get_oof_path(self, cfg):\n        return Path('../input/rsna-oof/charm/')/f'{cfg.name}.csv'\n    \n    def get_dataloader(self, cfg, test_df, batch_size=2):\n        model_name = cfg.name\n        model_path = cfg.model_path\n        cfg_path = f\"{model_path}/fold_0/config.yaml\"\n        cfg = OmegaConf.create(yaml.safe_load(open(cfg_path)))\n        dataset = RSNADataset(test_df, cfg)\n        test_dataloader = DataLoader(dataset, batch_size=batch_size, shuffle=False, num_workers=2, pin_memory=False)\n        return test_dataloader\n    \n    def infer_model(self, config, test_dataloader, folds=[0,1,2,3]):\n        model_name = config.name\n        model_path = config.model_path\n        cfg_path_list = [f\"{model_path}/fold_{i}/config.yaml\" for i in folds]\n        cfg_list = [OmegaConf.create(yaml.safe_load(open(cfg_path))) for cfg_path in cfg_path_list]\n        if DEBUG:\n            model_path_list = [f\"{model_path}/fold_{DEBUG_FOLD}/model_weights_ema.pth\" for i in folds]\n        else:\n            model_path_list = [f\"{model_path}/fold_{i}/model_weights_ema.pth\" for i in folds]\n        predictor_list = []\n        for model_path, cfg in zip(model_path_list, cfg_list):\n            print(\"Loading model from\", model_path)\n            predictor = Forwarder(init_model_from_config(cfg.model, model_path), cfg)\n            predictor.eval()\n            predictor.to(\"cuda\")\n            predictor_list.append(predictor)\n        \n        use_multi_lat = cfg_list[0].dataset.use_multi_lat\n        prediction_ids_lr_list = [() for _ in range(len(predictor_list))]\n        preds_list = [[] for _ in range(len(predictor_list))]\n        if use_multi_lat:\n            prediction_ids_l_list = [() for _ in range(len(predictor_list))]\n            prediction_ids_r_list = [() for _ in range(len(predictor_list))]\n            preds_l_list = [[] for _ in range(len(predictor_list))]\n            preds_r_list = [[] for _ in range(len(predictor_list))]\n        print(\"inference started\")\n        for inputs, prediction_ids in tqdm(test_dataloader):\n            with torch.cuda.amp.autocast(enabled=True):\n                inputs = inputs.to(\"cuda\")\n                with torch.no_grad():\n                    if use_multi_lat:\n                        for i in range(len(predictor_list)):\n                            logits_l, logits_r = predictor_list[i](inputs)\n                            preds_l_list[i].append(logits_l.sigmoid().cpu().detach().numpy().reshape(-1))\n                            preds_r_list[i].append(logits_r.sigmoid().cpu().detach().numpy().reshape(-1))\n                            prediction_ids_l_list[i] += prediction_ids[0]\n                            prediction_ids_r_list[i] += prediction_ids[1]\n                    else:\n                        for i in range(len(predictor_list)):\n                            logits = predictor_list[i](inputs)\n                            preds_list[i].append(logits.sigmoid().cpu().detach().numpy().reshape(-1))\n                            prediction_ids_lr_list[i] += prediction_ids[0]\n        torch.cuda.empty_cache()\n        print(\"inference finished\")\n        if use_multi_lat:\n            for i in range(len(predictor_list)):\n                preds_l_list[i] = np.hstack(preds_l_list[i])\n                preds_r_list[i] = np.hstack(preds_r_list[i])\n                preds_list[i] = np.hstack([preds_l_list[i], preds_r_list[i]])\n                prediction_ids_lr_list[i] = prediction_ids_l_list[i] + prediction_ids_r_list[i]\n        else:\n            for i in range(len(predictor_list)):\n                preds_list[i] = np.hstack(preds_list[i])\n        for predictor in predictor_list:\n            torch.cuda.empty_cache()\n            del predictor; gc.collect()\n        torch.cuda.empty_cache()\n        pred_df_list = []\n        for ifold, (preds, prediction_ids_lr) in enumerate(zip(preds_list, prediction_ids_lr_list)):\n            pred_df = pd.DataFrame(data={\"prediction_id\": prediction_ids_lr, f\"pred{ifold}\": preds})\n            pred_df_list.append(pred_df)\n\n        return pred_df_list\n    \n    def apply_threshold(\n        self,\n        cfg, \n        raw_predictions, \n        test_sites, \n        mode='OOF',\n        threshold_params={}):\n        def load_precalculated_oof(path):\n            oof = pd.read_csv(path)[['prediction_id', 'pred', 'site_id', 'fold']].merge(\n                train[['prediction_id', 'target', 'difficult_negative_case']], on='prediction_id', how='left')\n            return {\n                'oof_labels': oof['target'].values,\n                'oof_preds': oof['pred'].values,\n                'oof_sites': oof['site_id'].values,\n                'oof_folds': oof['fold'].values,\n                'oof_hardflags': oof['difficult_negative_case'].astype(int).values\n            }\n\n        predictions = raw_predictions.copy()\n        folds = list(range(4))\n        precalculated_path = Path('../input/rsna-oof/charm/')/f'{cfg.name}.csv'\n        print(precalculated_path)\n        train = pd.read_csv('../input/rsna-misc/train_with_fold.csv').rename({'cancer': 'target'}, axis=1)\n        train['prediction_id'] = train['patient_id'].astype(str) + '_' + train['laterality']\n        train = train.groupby('prediction_id').agg({'target': 'max', 'fold': 'max', 'difficult_negative_case': 'max'}).reset_index()\n        prec_oof = load_precalculated_oof(precalculated_path)\n        thresholds = get_threshold(prec_oof, predictions, test_sites, mode, threshold_params)\n        return thresholds\n    \n    def inference_block(self, cfg, test_df, threshold_mode, fp16=False, threshold_params={}, batch_size=2, folds=[0,1,2,3]):\n        # Hard-code fix\n        test_master = test_df.copy()\n        test_master['prediction_id'] = test_master['patient_id'].astype(str) + '_' + test_master['laterality']\n        test_master = test_master.groupby('prediction_id').agg({'site_id': 'max'}).reset_index()\n        #\n        test_dataloader = self.get_dataloader(cfg, test_df, batch_size)\n        preds = self.infer_model(cfg, test_dataloader, folds)\n        torch.cuda.empty_cache()\n        preds = pd.concat([p.set_index('prediction_id') for p in preds], axis=1).reset_index()\n        preds = preds.merge(test_master, on='prediction_id', how='left')\n        preds_array = preds[['pred0', 'pred1', 'pred2', 'pred3']].T.values\n        preds[cfg.name] = preds_array.mean(0)\n        thresholds = self.apply_threshold(\n            cfg, preds_array, preds['site_id'].values, threshold_mode, threshold_params=threshold_params)\n        \n        return preds[['prediction_id', 'site_id', cfg.name]], thresholds","metadata":{"_kg_hide-input":true,"_kg_hide-output":true,"execution":{"iopub.status.busy":"2023-02-27T01:05:58.679001Z","iopub.execute_input":"2023-02-27T01:05:58.679646Z","iopub.status.idle":"2023-02-27T01:05:58.710309Z","shell.execute_reply.started":"2023-02-27T01:05:58.679594Z","shell.execute_reply":"2023-02-27T01:05:58.709237Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"def weighted_average(df, weights):\n    assert df.shape[1] == len(weights)\n    res = np.zeros(df.shape[0])\n    for i in range(len(weights)):\n        res += df.iloc[:, i].values * weights[i] / sum(weights)\n    return res\n\n\ndef inference_stage(configs, predictors, test_df, threshold_mode, threshold_params={}):\n    thresholds_all = []\n    model_names = []\n    for icfg, (cfg, pred) in enumerate(zip(configs, predictors)):\n        results, thresholds = pred.inference_block(\n            cfg, test_df, threshold_mode, fp16=FP16, threshold_params=threshold_params)\n        print(cfg.name, thresholds)\n        thresholds_all.append(thresholds)\n        model_names.append(cfg.name)\n        if icfg == 0:\n            results_all = results\n        else:\n            results_all = results_all.merge(results.drop('site_id', axis=1), on='prediction_id', how='left')\n    return results_all, thresholds_all\n","metadata":{"execution":{"iopub.status.busy":"2023-02-27T01:05:58.712062Z","iopub.execute_input":"2023-02-27T01:05:58.712504Z","iopub.status.idle":"2023-02-27T01:05:58.726032Z","shell.execute_reply.started":"2023-02-27T01:05:58.712466Z","shell.execute_reply":"2023-02-27T01:05:58.724826Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# Ariyasu pipeline mod\ntest[\"prediction_id\"] = test[\"patient_id\"].astype(str) + \"_\" + test[\"laterality\"]\ntest['path'] = '/tmp/output/' + test.patient_id.astype(str) + '_' + test.image_id.astype(str) + '.png'","metadata":{"execution":{"iopub.status.busy":"2023-02-27T01:05:58.72745Z","iopub.execute_input":"2023-02-27T01:05:58.727946Z","iopub.status.idle":"2023-02-27T01:05:58.742304Z","shell.execute_reply.started":"2023-02-27T01:05:58.727906Z","shell.execute_reply":"2023-02-27T01:05:58.741305Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# config collections\nclass charm_convnext_small_multi_lat: # LB 0.60\n    name = 'charm_convnext_small_multi_lat'\n    model_path = \"/kaggle/input/rsna-mammo-convnext-small-multi-lat-2/convnext_small_multi_lat_2\"\n\n\nclass charm_convnext_small_multi_view: # LB 0.62\n    name = 'charm_convnext_small_multi_view'\n    model_path = \"/kaggle/input/rsna-mammo-convnext-small-multi-view/convnext_small_multi_view\"\n    \n    \nclass charm_convnext_small_multi_view_2: # LB 0.60\n    name = 'charm_convnext_small_multi_view_2'\n    model_path = \"/kaggle/input/rsna-mammo-convnext-small-multi-view-2/convnext_small_multi_view_2\"\n\n    \nclass charm_convnext_small_multi_lat_1536: # LB ?\n    name = 'charm_convnext_small_multi_lat_1536'\n    model_path = \"/kaggle/input/rsna-mammo-convnext-small-multi-lat-1536/convnext_small_multi_lat_1536\"\n\n    \nclass charm_convnext_base_multi_lat_1024: # LB ?\n    name = 'charm_convnext_base_multi_lat_1024'\n    model_path = \"/kaggle/input/rsna-mammo-convnext-base-multi-lat-1024/convnext_base_multi_lat_1024\"\n\n\n# cfg = ariyasu_nano_config()\n# cfg = ariyasu_nextvit_base_config()\n# cfg = ariyasu_xlarge_config()\n# cfg = ariyasu_large_config()\n\n# cfg = IshikeiCkpt04size1536Avg()\n# cfg = IshikeiCkpt04size1536Avg()","metadata":{"execution":{"iopub.status.busy":"2023-02-27T01:05:58.743897Z","iopub.execute_input":"2023-02-27T01:05:58.744373Z","iopub.status.idle":"2023-02-27T01:05:58.753966Z","shell.execute_reply.started":"2023-02-27T01:05:58.744339Z","shell.execute_reply":"2023-02-27T01:05:58.752835Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"FP16 = True\nEXTEND = 0\nTHRESHOLD_MODE = 'value'\nDROP_MODE = 'PER_BREAST' # 'PER_PATIENT' To be implemented\nkuma = KumaInference()\nishikei = IshikeiInference()\nariyasu = AriyasuInference()\ncharm = CharmInterface()\n\n\n# # 0.5812973662009894\n# ensemble_configs = [ \n#     {\n#         'configs': [Res02mod3(), charm_convnext_small_multi_lat()],\n#         'predictors': [kuma, charm],\n#         'global_weights': [1, 1],\n#         'drop_rate': 0.9,\n#         'criteria': 'low value',\n#         'threshold': 'PROPORTIONAL_MEAN_PER_SITE',\n#     },\n#     {\n# #         'configs': [Res02lr0(), IshikeiCkpt07Avg(), IshikeiCkpt04Avg2()],\n#         'configs': [IshikeiCkpt07Avg()],\n#         'predictors': [ishikei],\n#         'global_weights': [1],\n#         'drop_rate': 0.5,\n#         'criteria': 'low value',\n#         'threshold': 'PROPORTIONAL_MEAN_PER_SITE',\n#     },\n#     {\n#         'configs': [ariyasu_nextvit_base_config(), ariyasu_large_config(), ariyasu_xlarge_config()],\n#         'predictors': [ariyasu, ariyasu, ariyasu],\n#         'global_weights': [3, 3, 2],\n#         'drop_rate': None,\n#         'criteria': 'low value',\n#         'threshold': 'OOF_MEAN_PER_SITE_EASY',\n#     },\n# ]\n\nensemble_configs = [ \n    {\n        'configs': [Res02mod3(), charm_convnext_small_multi_lat()],\n        'predictors': [kuma, charm],\n        'global_weights': [1, 1],\n        'drop_rate': 0.9,\n        'criteria': 'low value',\n        'threshold': 'PROPORTIONAL_MEAN_PER_SITE',\n    },\n    {\n#         'configs': [Res02lr0(), IshikeiCkpt07Avg(), IshikeiCkpt04Avg2()],\n        'configs': [IshikeiCkpt07Avg(), Res02lr0(), IshikeiCkpt04Avg2()],\n        'predictors': [ishikei, kuma, ishikei],\n        'global_weights': [1,1,1],\n        'drop_rate': 0.5,\n        'criteria': 'low value',\n        'threshold': 'PROPORTIONAL_MEAN_PER_SITE',\n    },\n    {\n        'configs': [ariyasu_nextvit_base_config(), ariyasu_large_config(), ariyasu_xlarge_config()],\n        'predictors': [ariyasu, ariyasu, ariyasu],\n        'global_weights': [1, 1, 1],\n        'drop_rate': None,\n        'criteria': 'low value',\n        'threshold': 'OOF_MEAN_PER_SITE_EASY',\n    },\n]","metadata":{"execution":{"iopub.status.busy":"2023-02-27T01:05:58.755599Z","iopub.execute_input":"2023-02-27T01:05:58.75599Z","iopub.status.idle":"2023-02-27T01:05:58.769947Z","shell.execute_reply.started":"2023-02-27T01:05:58.755956Z","shell.execute_reply":"2023-02-27T01:05:58.768933Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"test['prediction_id'] = test['patient_id'].astype(str) + '_' + test['laterality']\ntest.head()","metadata":{"execution":{"iopub.status.busy":"2023-02-27T01:05:58.771515Z","iopub.execute_input":"2023-02-27T01:05:58.771934Z","iopub.status.idle":"2023-02-27T01:05:58.800369Z","shell.execute_reply.started":"2023-02-27T01:05:58.771824Z","shell.execute_reply":"2023-02-27T01:05:58.799483Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"test2 = test.copy()\ntest_agg = test.groupby('prediction_id').agg({'view': 'count'})\nweights_all = []\nmodels_all = []\nthresholds_all = []\nfinal_results = []\nfor istage, stage_cfg in enumerate(ensemble_configs):\n    print(f'[STAGE {istage}: {len(test2)} records] ')\n    \n    for cfg in stage_cfg['configs']: # Ariyasu mod\n        if hasattr(cfg, 'file_name'):\n            cfg.name = cfg.file_name\n    \n    models_all += [cfg.name for cfg in stage_cfg['configs']]\n    weights_all += stage_cfg['global_weights']\n    \n    results_stage, thresholds_stage = inference_stage(\n        stage_cfg['configs'], stage_cfg['predictors'], test2, stage_cfg['threshold'], threshold_params={'total_size': len(test_agg)})\n    thresholds_all += thresholds_stage\n    if istage == 0:\n        results_all = results_stage\n    else:\n        results_all = results_all.merge(results_stage, on=['prediction_id', 'site_id'], how='inner')\n    results_all['cancer'] = weighted_average(results_all[models_all], weights_all)\n    if DEBUG:\n        print('DEBUG', istage, results_all.shape)\n        display(results_all.head())\n        print(models_all, weights_all)\n        print(thresholds_all)\n    \n    if stage_cfg['criteria'] == 'low value':\n        if stage_cfg['drop_rate'] is not None:\n            drop_results = results_all.loc[\n                results_all['cancer'] <= np.percentile(results_all['cancer'], stage_cfg['drop_rate']*100)].copy()\n            drop_results['cancer'] = 0.\n            final_results.append(drop_results)\n            test2 = test2.loc[~test2['prediction_id'].isin(drop_results['prediction_id'])]\n        else: # final stage\n#             thresholds = np.average(np.array(thresholds_all), axis=0, weights=weights_all)\n            final_results.append(results_all)\n        \n    else:\n        raise NotImplementedError()\nfinal_results = pd.concat(final_results, axis=0)","metadata":{"execution":{"iopub.status.busy":"2023-02-27T01:05:58.80164Z","iopub.execute_input":"2023-02-27T01:05:58.801964Z","iopub.status.idle":"2023-02-27T01:11:03.464364Z","shell.execute_reply.started":"2023-02-27T01:05:58.801932Z","shell.execute_reply":"2023-02-27T01:11:03.463091Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"def simulate_threshold(ensemble_configs, threshold_mode, final_results=None):\n    def merge_all(dfs):\n        for i, df in enumerate(dfs):\n            if 'target' in df.columns:\n                df.drop('target', axis=1, inplace=True)\n            if i == 0:\n                res = df.rename({'pred': models_all[i]}, axis=1)\n            else:\n                res = res.merge(df.rename({'pred': models_all[i]}, axis=1), on=['prediction_id', 'fold', 'site_id'], how='left')\n        return res.reset_index()\n    \n    train = pd.read_csv('../input/rsna-misc/train_with_fold.csv').rename({'cancer': 'target'}, axis=1)\n    train['prediction_id'] = train['patient_id'].astype(str) + '_' + train['laterality']\n    train = train.groupby('prediction_id').agg(\n        {'target': 'max', 'fold': 'max', 'difficult_negative_case': 'max'}).reset_index()\n    metric = PercentilePfbeta(percentile_range=(97.5, 99.0), n_trials=30)\n    \n    oof_agg_fold = {0: [], 1: [], 2: [], 3:[]}\n    models_all = []\n    weights_all = []\n    for stage_cfg in ensemble_configs:\n        for cfg in stage_cfg['configs']: # Ariyasu mod\n            if hasattr(cfg, 'file_name'):\n                cfg.name = cfg.file_name\n        for cfg, predictor in zip(\n            stage_cfg['configs'], stage_cfg['predictors']):\n            oof = pd.read_csv(predictor.get_oof_path(cfg))\n            models_all.append(cfg.name)\n            for fold in range(4):\n                oof_agg_fold[fold].append(oof.query('fold == @fold'))\n        weights_all += stage_cfg['global_weights']\n    thresholds_site1 = []\n    thresholds_site2 = []\n    print(models_all, weights_all)\n    for fold in range(4):\n        merged_fold = merge_all(oof_agg_fold[fold]).merge(\n            train[['prediction_id', 'target']], on='prediction_id', how='left')\n        # display(merged_fold)\n        oof_pred = weighted_average(merged_fold[models_all], weights_all)\n        oof_target = merged_fold['target'].values\n        oof_site = merged_fold['site_id'].values\n        score_1, percentile_1, threshold_1 = metric(oof_pred[oof_site == 1], oof_target[oof_site == 1])\n        score_2, percentile_2, threshold_2 = metric(oof_pred[oof_site == 2], oof_target[oof_site == 2])\n        if threshold_mode == 'value':\n            thresholds_site1.append(threshold_1)\n            thresholds_site2.append(threshold_2)\n        elif threshold_mode == 'proportional':\n            thresholds_site1.append(percentile_1)\n            thresholds_site2.append(percentile_2)\n    if threshold_mode == 'value':\n        return [np.mean(thresholds_site1), np.mean(thresholds_site2)]\n    elif threshold_mode == 'proportional':\n        print(thresholds_site1, thresholds_site2)\n        threshold_site1 = np.percentile(\n            final_results.query('site_id == 1')['cancer'].values, np.mean(thresholds_site1))\n        threshold_site2 = np.percentile(\n            final_results.query('site_id == 2')['cancer'].values, np.mean(thresholds_site2))\n        return [threshold_site1, threshold_site2]","metadata":{"execution":{"iopub.status.busy":"2023-02-27T01:11:03.471538Z","iopub.execute_input":"2023-02-27T01:11:03.471874Z","iopub.status.idle":"2023-02-27T01:11:13.53806Z","shell.execute_reply.started":"2023-02-27T01:11:03.471846Z","shell.execute_reply":"2023-02-27T01:11:13.530714Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# thresholds = simulate_threshold(ensemble_configs, THRESHOLD_MODE, final_results)\nthresholds = [0.28, 0.23]","metadata":{"execution":{"iopub.status.busy":"2023-02-27T01:11:13.539475Z","iopub.execute_input":"2023-02-27T01:11:13.54026Z","iopub.status.idle":"2023-02-27T01:11:17.899633Z","shell.execute_reply.started":"2023-02-27T01:11:13.540222Z","shell.execute_reply":"2023-02-27T01:11:17.898507Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"results_all","metadata":{"execution":{"iopub.status.busy":"2023-02-27T01:11:17.901285Z","iopub.execute_input":"2023-02-27T01:11:17.901685Z","iopub.status.idle":"2023-02-27T01:11:17.919204Z","shell.execute_reply.started":"2023-02-27T01:11:17.901648Z","shell.execute_reply":"2023-02-27T01:11:17.918161Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"final_results","metadata":{"execution":{"iopub.status.busy":"2023-02-27T01:11:17.920577Z","iopub.execute_input":"2023-02-27T01:11:17.921592Z","iopub.status.idle":"2023-02-27T01:11:17.947652Z","shell.execute_reply.started":"2023-02-27T01:11:17.921552Z","shell.execute_reply":"2023-02-27T01:11:17.946579Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"final_results.loc[\n    final_results['site_id'] == 1, 'cancer'] = \\\n        (final_results.loc[final_results['site_id'] == 1, 'cancer'] > thresholds[0]).astype(float)\nfinal_results.loc[final_results['site_id'] == 2, 'cancer'] = \\\n        (final_results.loc[final_results['site_id'] == 2, 'cancer'] > thresholds[1]).astype(float)","metadata":{"execution":{"iopub.status.busy":"2023-02-27T01:11:17.949326Z","iopub.execute_input":"2023-02-27T01:11:17.949722Z","iopub.status.idle":"2023-02-27T01:11:17.961573Z","shell.execute_reply.started":"2023-02-27T01:11:17.949685Z","shell.execute_reply":"2023-02-27T01:11:17.960476Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"final_results[['prediction_id', 'cancer']].to_csv('submission.csv', index=False)","metadata":{"execution":{"iopub.status.busy":"2023-02-27T01:11:17.963165Z","iopub.execute_input":"2023-02-27T01:11:17.963616Z","iopub.status.idle":"2023-02-27T01:11:17.976164Z","shell.execute_reply.started":"2023-02-27T01:11:17.96358Z","shell.execute_reply":"2023-02-27T01:11:17.975239Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"if DEBUG:\n    from sklearn.metrics import f1_score\n    \n    tr=pd.read_csv('/kaggle/input/rsna-breast-cancer-detection/train.csv')\n    tr['prediction_id'] = tr.patient_id.astype(str) + '_' + tr.laterality\n    df = final_results.merge(tr[['cancer', 'prediction_id']], on='prediction_id')\n    print('f1:', f1_score(df.cancer_y, df.cancer_x))    \n","metadata":{"execution":{"iopub.status.busy":"2023-02-27T01:11:17.979407Z","iopub.execute_input":"2023-02-27T01:11:17.979785Z","iopub.status.idle":"2023-02-27T01:11:18.116023Z","shell.execute_reply.started":"2023-02-27T01:11:17.979757Z","shell.execute_reply":"2023-02-27T01:11:18.114866Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# Clean env","metadata":{}},{"cell_type":"code","source":"!rm *.py\n!rm timm segmentation_models_pytorch kuma_utils global_objectives iterstrat\n!rm dummy_bbox.csv","metadata":{"execution":{"iopub.status.busy":"2023-02-27T01:11:18.117835Z","iopub.execute_input":"2023-02-27T01:11:18.118607Z","iopub.status.idle":"2023-02-27T01:11:21.188578Z","shell.execute_reply.started":"2023-02-27T01:11:18.118565Z","shell.execute_reply":"2023-02-27T01:11:21.187195Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"","metadata":{},"execution_count":null,"outputs":[]}]}