{"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":"code","source":"import os\nif not os.path.isfile(\"./nvjpeg2k.so\"):\n    !cp /kaggle/input/nvjpeg2k/nvjpeg2k.so ./\n    !pip install -q --disable-pip-version-check /kaggle/input/rsna-2022-whl/pylibjpeg-1.4.0-py3-none-any.whl\n    !pip install -q --disable-pip-version-check /kaggle/input/rsna-2022-whl/python_gdcm-3.0.15-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl\n    !pip install -q --disable-pip-version-check /kaggle/input/rsnawhls/timm-0.6.12-py3-none-any.whl\n    !pip install -q --disable-pip-version-check /kaggle/input/rsnawhls/dicomsdl-0.109.1-cp37-cp37m-manylinux_2_12_x86_64.manylinux2010_x86_64.whl\n    !cp -r /kaggle/input/timmmaster/pytorch-image-models-main /tmp/pytorch-image-models-main && pip install -q --disable-pip-version-check  /tmp/pytorch-image-models-main\n    !pip install -q --disable-pip-version-check /kaggle/input/rsna-packages/einops-0.6.0-py3-none-any.whl\n    !pip install -q --disable-pip-version-check /kaggle/input/rsna-packages/pydicom-2.3.1-py3-none-any.whl","metadata":{"_uuid":"7dc10552-ed66-4f02-b9b4-c37e70bf16b4","_cell_guid":"7c4b395a-f646-4231-89a7-7026d588599b","collapsed":false,"jupyter":{"outputs_hidden":false},"execution":{"iopub.status.busy":"2023-02-23T03:05:07.089592Z","iopub.execute_input":"2023-02-23T03:05:07.090075Z","iopub.status.idle":"2023-02-23T03:06:29.59276Z","shell.execute_reply.started":"2023-02-23T03:05:07.09003Z","shell.execute_reply":"2023-02-23T03:06:29.591564Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"import sys\nsys.path.insert(0, \"/kaggle/input/chenglu-rsna-code/rsna-2022\")","metadata":{"_uuid":"33576f41-7d68-41f3-ab17-fe98064c1cfe","_cell_guid":"2ddf8aad-9d8c-4c2e-a230-23071c78dfbc","collapsed":false,"jupyter":{"outputs_hidden":false},"execution":{"iopub.status.busy":"2023-02-23T03:05:01.254366Z","iopub.execute_input":"2023-02-23T03:05:01.254824Z","iopub.status.idle":"2023-02-23T03:05:01.326993Z","shell.execute_reply.started":"2023-02-23T03:05:01.254736Z","shell.execute_reply":"2023-02-23T03:05:01.326091Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"from models import EffnetAuxHead\nfrom pydicom.filebase import DicomBytesIO\nimport pydicom\nfrom tqdm.notebook import tqdm\nfrom joblib import Parallel, delayed\nfrom pathlib import Path\nimport pandas as pd\nimport os\nimport matplotlib.pyplot as plt\nimport nvjpeg2k\nimport torch\nimport numpy as np\nimport timm\nimport albumentations as A\nimport dicomsdl\nfrom pydicom.pixel_data_handlers import apply_voi_lut\nimport cv2\nimport multiprocessing\nfrom time import time\nimport utils\nimport models\nfrom copy import copy","metadata":{"_uuid":"ad1d6f49-a2e5-40f9-918b-02a2d9567e0b","_cell_guid":"c1a2fd4b-0cb7-49b0-916b-5444218d4cbc","collapsed":false,"jupyter":{"outputs_hidden":false},"execution":{"iopub.status.busy":"2023-02-23T03:14:13.833685Z","iopub.execute_input":"2023-02-23T03:14:13.83439Z","iopub.status.idle":"2023-02-23T03:14:13.84074Z","shell.execute_reply.started":"2023-02-23T03:14:13.834354Z","shell.execute_reply":"2023-02-23T03:14:13.839671Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"class Predictor:\n\n    def __init__(self, model, size, flip=None, rotate=None, tta=None, weight=1, use_roi=False, use_lut=False):\n        self.model = model\n        self.size = size\n        self.model.eval()\n        self.flip = flip\n        self.rotate = rotate\n        self.tta = tta\n        self.weight = weight\n        self.use_roi = use_roi\n        self.use_lut = use_lut\n\n    def transform(self, image, flip=None, rotate=None):\n        image = image.copy()\n        if self.flip == \"y\":\n            image = image[::-1, :]\n        if self.flip == \"x\":\n            image = image[:, ::-1]\n        if self.rotate == 90:\n            image = cv2.rotate(image, cv2.ROTATE_90_CLOCKWISE)\n        if self.rotate == 180:\n            image = cv2.rotate(image, cv2.ROTATE_180)\n        if self.rotate == 270:\n            image = cv2.rotate(image, cv2.ROTATE_90_COUNTERCLOCKWISE)\n        return image\n\n    @torch.no_grad()\n    def __call__(self, image):\n        image = cv2.resize(image, (self.size, self.size))\n        images = [self.transform(image, self.flip, self.rotate)]\n        if self.tta:\n            for tta in self.tta.split(\"&\"):\n                k, v = tta.split(\"=\")\n                images.append(self.transform(image, **{k: v}))\n        images = np.stack(images)\n        images = torch.from_numpy(images).unsqueeze(1) / 255.0\n        logits = self.model(images.cuda())\n        return logits.cpu()\n\n\nclass Ensembler:\n\n    def __init__(self, predictors, mode, threshold=None, counts=None):\n        self.predictors = predictors\n        self.threshold = threshold\n        self.denominator = sum([p.weight for p in self.predictors])\n        self.mode = mode\n        self.counts = counts\n        if self.mode == \"max\":\n            assert self.counts is not None and self.threshold is not None\n        if self.mode == \"weighted_average\":\n            assert self.threshold is not None\n\n    def __call__(self, image):\n        if self.mode == \"weighted_average\":\n            all_probs = []\n            for predictor in self.predictors:\n                logits = predictor(image)\n                all_probs.append(torch.sigmoid(logits).mean() * predictor.weight / self.denominator)\n            all_probs = torch.stack(all_probs)\n            return all_probs.sum().item() > self.threshold\n        elif self.mode == \"max\":\n            all_probs = []\n            for predictor in self.predictors:\n                logits = predictor(image).squeeze(1)\n                all_probs.append(torch.sigmoid(logits))\n            all_probs = torch.cat(all_probs)\n            return (all_probs > self.threshold).sum().item() >= self.counts\n        elif self.mode == \"plain\":\n            all_probs = []\n            for predictor in self.predictors:\n                logits = predictor(image).squeeze(1)\n                all_probs.append(torch.sigmoid(logits))\n            all_probs = torch.cat(all_probs)\n            return all_probs.cpu().tolist()\n\n\ndef normalised_to_8bit(image):\n    xmin = image.min()\n    xmax = image.max()\n    norm = np.empty_like(image, dtype=np.uint8)\n    dicomsdl.util.convert_to_uint8(image, norm, xmin, xmax)\n    return norm\n\n\ndef create_model(model_name, checkpoint_path):\n    if isinstance(model_name, str):\n        model = timm.create_model(model_name, in_chans=1, num_classes=1)\n    else:\n        model = model_name\n    model.load_state_dict(torch.load(checkpoint_path, map_location=\"cpu\"))\n    model.cuda()\n    model.eval()\n    return model","metadata":{"_uuid":"6f6cf1b0-73e0-4da3-8868-6e6e4c1efab5","_cell_guid":"8a0fcb1b-3bcc-42a8-a6ad-33b7e074563b","collapsed":false,"jupyter":{"outputs_hidden":false},"execution":{"iopub.status.busy":"2023-02-23T03:29:38.167886Z","iopub.execute_input":"2023-02-23T03:29:38.168245Z","iopub.status.idle":"2023-02-23T03:29:38.190993Z","shell.execute_reply.started":"2023-02-23T03:29:38.168215Z","shell.execute_reply":"2023-02-23T03:29:38.190071Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"class Convnext(torch.nn.Module):\n\n    def create_pool_head(self, ori_head, dim, num_classes):\n        return copy(ori_head[:3]), torch.nn.Linear(dim, num_classes)\n\n    def __init__(self, with_aux_features, with_aux_targets, variant, in_chans, *args, **kwargs):\n        super().__init__()\n        self.in_chans = in_chans\n        self.with_aux_features = with_aux_features\n        self.with_aux_targets = with_aux_targets\n        channels, name = {\n            \"nano\": ((80, 120, 320, 640), \"convnextv2_nano.fcmae_ft_in22k_in1k\"),\n            \"base\": ((128, 256, 512, 1024), \"convnextv2_base.fcmae_ft_in22k_in1k\"),\n        }[variant]\n\n        use_pretrained = os.environ.get(\"KAGGLE_KERNEL_RUN_TYPE\") is None\n        backbone = timm.create_model(\n            name, pretrained=use_pretrained, in_chans=in_chans, num_classes=1\n        )\n        self.stem = backbone.stem\n        self.stages = backbone.stages\n        feat_dim = channels[-1]\n        if self.with_aux_features:\n            feat_dim += 40\n            self.meta_view = torch.nn.Embedding(6, 8)\n            self.meta_age = torch.nn.Embedding(64, 8)\n            self.meta_implant = torch.nn.Embedding(2, 8)\n            self.meta_site_id = torch.nn.Embedding(2, 8)\n            self.meta_laterality = torch.nn.Embedding(2, 8)\n        if self.with_aux_targets:\n            self.density_pool, self.density_fc = self.create_pool_head(backbone.head, feat_dim, 1)\n            self.biopsy_pool, self.biopsy_fc = self.create_pool_head(backbone.head, feat_dim, 1)\n            self.invasive_pool, self.invasive_fc = self.create_pool_head(backbone.head, feat_dim, 1)\n            self.BIRADS_pool, self.BIRADS_fc = self.create_pool_head(backbone.head, feat_dim, 4)\n            self.difficult_negative_case_pool, self.difficult_negative_case_fc = self.create_pool_head(backbone.head, feat_dim, 1)\n        self.cancer_pool, self.cancer_fc = self.create_pool_head(backbone.head, feat_dim, 1)\n\n    def forward(self, x, aux_features=None):\n        if self.in_chans == 3:\n            x = x.repeat(1, 3, 1, 1)\n        x = self.stem(x)\n        feature_map = self.stages(x)\n\n        cf = self.cancer_pool(feature_map)\n        if self.with_aux_targets:\n            df = self.density_pool(feature_map)\n            bf = self.biopsy_pool(feature_map)\n            if_ = self.invasive_pool(feature_map)\n            Bf = self.BIRADS_pool(feature_map)\n            dncf = self.difficult_negative_case_pool(feature_map)\n\n        if self.with_aux_features:\n            vie_emb = self.meta_view(aux_features[\"view\"]).squeeze(1)\n            age_emb = self.meta_age(aux_features[\"age\"]).squeeze(1)\n            imp_emb = self.meta_implant(aux_features[\"implant\"]).squeeze(1)\n            sid_emb = self.meta_site_id(aux_features[\"site_id\"]).squeeze(1)\n            lat_emb = self.meta_laterality(aux_features[\"laterality\"]).squeeze(1)\n\n            cf = torch.cat((cf, vie_emb, age_emb, imp_emb, sid_emb, lat_emb), dim=1)\n            if self.with_aux_targets:\n                df = torch.cat((df, vie_emb, age_emb, imp_emb, sid_emb, lat_emb), dim=1)\n                bf = torch.cat((bf, vie_emb, age_emb, imp_emb, sid_emb, lat_emb), dim=1)\n                if_ = torch.cat((if_, vie_emb, age_emb, imp_emb, sid_emb, lat_emb), dim=1)\n                Bf = torch.cat((Bf, vie_emb, age_emb, imp_emb, sid_emb, lat_emb), dim=1)\n                dncf = torch.cat((dncf, vie_emb, age_emb, imp_emb, sid_emb, lat_emb), dim=1)\n\n        ret = {\"cancer\": self.cancer_fc(cf)}\n        if self.with_aux_targets:\n            ret[\"density\"] = self.density_fc(df)\n            ret[\"biopsy\"] = self.biopsy_fc(bf)\n            ret[\"invasive\"] = self.invasive_fc(if_)\n            ret[\"BIRADS\"] = self.BIRADS_fc(Bf)\n            ret[\"difficult_negative_case\"] = self.difficult_negative_case_fc(dncf)\n\n        return ret[\"cancer\"]","metadata":{"execution":{"iopub.status.busy":"2023-02-23T03:29:38.501593Z","iopub.execute_input":"2023-02-23T03:29:38.501916Z","iopub.status.idle":"2023-02-23T03:29:38.52039Z","shell.execute_reply.started":"2023-02-23T03:29:38.50189Z","shell.execute_reply":"2023-02-23T03:29:38.519268Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"def main(part):\n    os.environ[\"CUDA_VISIBLE_DEVICES\"] = str(part)\n\n    nano_1536_98c42ccd = create_model( # 0.52\n        Convnext(with_aux_features=False, with_aux_targets=False, variant=\"nano\", in_chans=3),\n        \"/kaggle/input/real-final-test/98c42ccd_model_9_0.8571.pt\"\n    )\n\n    nano_1536_76ba575e = create_model( # 0.51\n        Convnext(with_aux_features=False, with_aux_targets=False, variant=\"nano\", in_chans=3),\n        \"/kaggle/input/real-final-test/76ba575e_model_29_0.8000.pt\"\n    )\n\n    nano_2048_7e38f520 = create_model(  # 0.56\n        Convnext(with_aux_features=False, with_aux_targets=False, variant=\"nano\", in_chans=3),\n        \"/kaggle/input/real-final-test/7e38f520_model_40_1.0000.pt\"\n    )\n\n    eff_1536_9409f8b5 = create_model( # 0.54\n        timm.create_model(\"tf_efficientnetv2_s\", num_classes=1, in_chans=1),\n        \"/kaggle/input/real-final-test/9409f8b5_model_15_0.9231.pt\"\n    )\n\n    ensembler = Ensembler([\n        Predictor(nano_1536_98c42ccd, 1536, flip=\"x\"),\n        Predictor(nano_1536_76ba575e, 1536, flip=\"y\"),\n        Predictor(nano_2048_7e38f520, 2048, rotate=\"90\"),\n        Predictor(nano_1536_76ba575e, 1536),\n    ], mode=\"weighted_average\", threshold=0.57)\n\n    # 主进程划分 patient_id 为两半，然后绑定在 main 上，不同的进程读取各自的 patient_id，处理完后生成各自的 submission.csv，然后再提交\n    df = pd.read_csv(f\"/kaggle/input/rsna-breast-cancer-detection/test.csv\", dtype={\"patient_id\": str, \"image_id\": str, \"laterality\": str})    \n    submission_df = pd.read_csv(\"/kaggle/input/rsna-breast-cancer-detection/sample_submission.csv\")\n\n    ############################## debug start ##############################\n    debug = len(os.listdir(\"/kaggle/input/rsna-breast-cancer-detection/test_images\")) == 1\n    if debug:\n        mock_dfs = []\n        mock_sub_dfs = []\n        for i in range(1, 10):\n            mock_df = df.copy()\n            patient_id = str(10000 + i)\n            mock_df.patient_id = patient_id\n            mock_dfs.append(mock_df)\n\n            mock_sub_df = submission_df.copy()\n            mock_sub_df[\"prediction_id\"] = [patient_id + \"_L\", patient_id + \"_R\"]\n            mock_sub_dfs.append(mock_sub_df)\n\n        df = pd.concat([df, *mock_dfs])\n        submission_df = pd.concat([submission_df, *mock_sub_dfs])\n    ############################## debug end ##############################\n\n    submission_df[\"patient_id\"] = submission_df.prediction_id.apply(lambda x: x.split(\"_\")[0])\n    patient_ids = df.patient_id.unique()\n    half_len = len(patient_ids) // 2\n\n    half_patient_ids = patient_ids[:half_len] if part == 0 else patient_ids[half_len:]\n    df = df.loc[df.patient_id.isin(half_patient_ids)].reset_index()    \n    submission_df = submission_df.loc[submission_df.patient_id.isin(half_patient_ids)].reset_index()\n\n    decoder = nvjpeg2k.Decoder()\n\n    def load_dicom(path, force_slow=False):\n        dcmfile = pydicom.dcmread(path)\n        reverse = dcmfile.PhotometricInterpretation == \"MONOCHROME1\"\n        if not force_slow and dcmfile.file_meta.TransferSyntaxUID == '1.2.840.10008.1.2.4.90':\n            with open(path, 'rb') as f:\n                raw = DicomBytesIO(f.read())\n                ds = pydicom.dcmread(raw)\n            offset = ds.PixelData.find(b\"\\x00\\x00\\x00\\x0C\")\n            hackedbitstream = bytearray()\n            hackedbitstream.extend(ds.PixelData[offset:])\n            return decoder.decode(hackedbitstream), reverse, dcmfile\n        else:\n            return dcmfile.pixel_array, reverse, dcmfile\n\n    def load_image_from_dicom(path):\n        image, reverse, _ = load_dicom(path, force_slow=False)\n        image = image.astype(np.float32)\n        image = normalised_to_8bit(image)\n        if reverse:\n            image = 255 - image\n        return image\n\n    plain_preds = []\n    start = time()\n    with torch.no_grad():\n        for i, item in tqdm(df.iterrows(), total=len(df)):\n            if debug:\n                image = load_image_from_dicom(f\"/kaggle/input/rsna-breast-cancer-detection/test_images/10008/{item.image_id}.dcm\")\n            else:\n                image = load_image_from_dicom(f\"/kaggle/input/rsna-breast-cancer-detection/test_images/{item.patient_id}/{item.image_id}.dcm\")\n\n            # crop and pad and resize\n            image = utils.crop_roi(image)\n            long_edge = max(image.shape[:2])\n            pad_fn = A.PadIfNeeded(long_edge, long_edge, border_mode=cv2.BORDER_CONSTANT, value=0, always_apply=True, p=1.0)\n            image = pad_fn(image=image)[\"image\"]\n            res = ensembler(image)\n            df.loc[i, \"hard_pred\"] = res\n    print(f\"Time usage: {time() - start}s\")\n\n    for i, item in submission_df.iterrows():\n        patient_id, laterality = item.prediction_id.split(\"_\")\n        submission_df.loc[i, \"cancer\"] = df.loc[(df.patient_id == patient_id) & (df.laterality == laterality)].hard_pred.any().astype(int)\n    submission_df.to_csv(f\"./part.{part}.csv\", index=False)","metadata":{"execution":{"iopub.status.busy":"2023-02-23T03:29:38.948272Z","iopub.execute_input":"2023-02-23T03:29:38.948581Z","iopub.status.idle":"2023-02-23T03:29:38.969248Z","shell.execute_reply.started":"2023-02-23T03:29:38.948554Z","shell.execute_reply":"2023-02-23T03:29:38.968247Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"process1 = multiprocessing.Process(target=main, args=(0,))\nprocess2 = multiprocessing.Process(target=main, args=(1,))\n\nprocess1.start()\nprocess2.start()\n\nprocess1.join()\nprocess2.join()","metadata":{"execution":{"iopub.status.busy":"2023-02-23T03:29:39.323117Z","iopub.execute_input":"2023-02-23T03:29:39.323476Z","iopub.status.idle":"2023-02-23T03:30:10.98488Z","shell.execute_reply.started":"2023-02-23T03:29:39.323446Z","shell.execute_reply":"2023-02-23T03:30:10.98351Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"sub_df0 = pd.read_csv(\"./part.0.csv\")\nsub_df1 = pd.read_csv(\"./part.1.csv\")","metadata":{"execution":{"iopub.status.busy":"2023-02-23T03:30:10.987295Z","iopub.execute_input":"2023-02-23T03:30:10.9876Z","iopub.status.idle":"2023-02-23T03:30:10.999876Z","shell.execute_reply.started":"2023-02-23T03:30:10.987569Z","shell.execute_reply":"2023-02-23T03:30:10.999002Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"pd.concat([sub_df0, sub_df1])[[\"prediction_id\", \"cancer\"]].to_csv(\"./submission.csv\", index=False)","metadata":{"execution":{"iopub.status.busy":"2023-02-23T03:30:11.001325Z","iopub.execute_input":"2023-02-23T03:30:11.002353Z","iopub.status.idle":"2023-02-23T03:30:11.013435Z","shell.execute_reply.started":"2023-02-23T03:30:11.002315Z","shell.execute_reply":"2023-02-23T03:30:11.012441Z"},"trusted":true},"execution_count":null,"outputs":[]}]}