{
  "id": 465815,
  "title": "9th place solution ",
  "url": "/competitions/UBC-OCEAN/discussion/465815",
  "author_name": "fate",
  "post_date": "2024-01-05T18:08:07.732000",
  "votes": 15,
  "comment_count": 3,
  "views": 0,
  "content": "<h3>Only use Competition Data, no External Data</h3>\n<h2><strong>Split WSI and TMA:</strong></h2>\n<p>WSI images have black pixels (all zeros in all three channels), while TMA images do not. Therefore, if both the image width and height are less than 6000, but the area of black pixels is greater than 5% of the image (all WSI images in the training data have more than 10% black pixels), it is classified as WSI; otherwise, it is classified as TMA.</p>\n<h2><strong>Make tile :</strong></h2>\n<p>First, reduce the size of the WSI by 0.33 times, and then divide it into 512*512 tiles. Subsequently, categorize these tiles into three levels based on the presence of bad pixels, identified by the condition \"np.sum(np.ptp(tile, axis=2) &lt; 20)\".<br>\nInference make tile code:</p>\n<pre><code> ():\n  path=\n  p_mask=\n  image=cv2.imread(path)\n  image=cv2.resize(image,(,),fx=scale,fy=scale,interpolation=cv2.INTER_AREA)\n  mask=np.load(p_mask)\n  os.makedirs(,exist_ok=)\n  count=\n   (count&lt;):\n      idxs=[(y,x)  y  (,image.shape[]//)  x  (,image.shape[]//)]\n      random.shuffle(idxs)\n       k, (y, x)  (idxs):\n          tile=image[y*:(y+)*,x*:(x+)*,:]\n          \n          bg_count=np.(np.ptp(tile,axis=)&lt;)\n           ((bg_count/(*))&lt;=):\n              cv2.imwrite(,tile)\n              count+=\n            count&gt;=: \n              \n       count&lt;:\n          idxs=[(y,x)  y  (,image.shape[]//)  x  (,image.shape[]//)]\n          random.shuffle(idxs)\n           k, (y, x)  (idxs):\n              tile=image[y*:(y+)*,x*:(x+)*,:]\n              \n              bg_count=np.(np.ptp(tile,axis=)&lt;)\n                ((bg_count/(*))&lt;=)&amp;((bg_count/(*))&gt;):\n                  cv2.imwrite(,tile)\n                  count+=\n                count&gt;=:\n                  \n       count&lt;:\n          idxs=[(y,x)  y  (,image.shape[]//)  x  (,image.shape[]//)]\n          random.shuffle(idxs)\n           k, (y, x)  (idxs):\n              tile=image[y*:(y+)*,x*:(x+)*,:]\n               \n              bg_count=np.(np.ptp(tile,axis=)&lt;)\n                ((bg_count/(*))&lt;=)&amp;((bg_count/(*))&gt;):\n                  cv2.imwrite(,tile)\n                  count+=\n                count&gt;=:\n                  \n</code></pre>\n<h4>Training tile:</h4>\n<p><strong>Step 1:</strong>Using all tiles if bg_count/area less 0.5<br>\n<strong>Step 2:</strong>If tiles of WSI image&lt;50,add ((bg_count/area) between 0.5-0.65)   tiles until there are 50 tiles.<br>\n<strong>Step 3:</strong>If tiles of WSI image&lt;20,add ((bg_count/area) between 0.65-0.75)  tiles until there are 20 tiles.</p>\n<h2>Model Training:</h2>\n<p>Only use WSI tiles. Randomly select 6 tiles from each image for training in every batch.<br>\nLoss Function: Binary Cross-Entropy (BCE)<br>\n<strong>Step 1:</strong> Normal Training<br>\n<strong>Step 2:</strong><br>\nUtilize the results from Step 1 to generate auxiliary labels.If the predicted value for true label is greater than 0.3, set the auxiliary label to 1; otherwise, set it to 0.<br>\nRe-train the model without using the weights from Step 1.<br>\nLoss function: Label loss (BCE) + 0.3 * Auxiliary Label loss (BCE)<br>\nLearning rate: 2e-4<br>\n<strong>Step 3:</strong> Fine-tuning with Step 2 Weights<br>\nFurther refine the model using the weights obtained from Step 2.<br>\nLoss function: Label loss (BCE) + 0.15 * Auxiliary Label loss (BCE)<br>\nLearning rate: 5e-5</p>\n<h4>Models with different backbone:</h4>\n<p>efficientnetb4,efficientnet_v2s,maxvit_tiny (The model settings of different backbones are slightly different.)</p>\n<h3>WSI</h3>\n<p>Use model to predict tiles.</p>\n<h3>Wsi tile ensemble:</h3>\n<pre><code>tile_df[]=np.(tile_df[[,,,,]],axis=)\ntile_df[]=np.argmax(tile_df[[,,,,]].values,axis=)\ntile_df=tile_df[[,,,]].groupby([,])[[,]].mean().reset_index()\nidx=tile_df.groupby([])[].idxmax()\nwsi_df=tile_df.loc[idx1].reset_index(drop=)\n</code></pre>\n<h3>Outliers(WSI):</h3>\n<p>The predicted mean value of aux_label&lt;0.5(The score is almost the same as not predict \"Other\",maybe+0.01)</p>\n<h2>tma:</h2>\n<p><strong>Step1.</strong>Crop tma</p>\n<pre><code> ():\n  ks=((img.shape[],img.shape[])//,)\n\n  mask=(img.(axis=)-img.(axis=))&gt;\n  kernel = np.ones((ks, ks),np.uint8)\n  mask=cv2.erode(mask.astype(np.uint8),kernel)\n  nonzero_pixels = np.column_stack(np.where(mask &gt; ))\n\n   (nonzero_pixels.size)&lt;(img.size//):\n       img\n  :\n\n      min_y, min_x = np.(nonzero_pixels, axis=)\n      max_y, max_x = np.(nonzero_pixels, axis=)\n        img[(,min_y-ks):max_y+ks+,(,min_x-ks):max_x+ks+,:]\n</code></pre>\n<p><strong>Step2.</strong>Resize to 512*512(The size of tma *0.33*0.5~512, so we can directly resize to 512 to predict)<br>\n<strong>Step3.</strong>Using wsi training model to predict</p>\n<h3>Outliers(tma):</h3>\n<p>The predicted value of aux_label &lt;0.5 (compared to tma without predict \"Other\", public score +0.03, private score +0.06)</p>\n<h2>Ensemble different models:</h2>\n<p>Voting(Compared with a single model, maybe only +0.01)</p>\n<h2>Maybe not work:</h2>\n<p>segmentation</p>",
  "messages": [
    {
      "id": 2588774,
      "postDate": "2024-01-05T18:08:07.733Z",
      "content": "<h3>Only use Competition Data, no External Data</h3>\n<h2><strong>Split WSI and TMA:</strong></h2>\n<p>WSI images have black pixels (all zeros in all three channels), while TMA images do not. Therefore, if both the image width and height are less than 6000, but the area of black pixels is greater than 5% of the image (all WSI images in the training data have more than 10% black pixels), it is classified as WSI; otherwise, it is classified as TMA.</p>\n<h2><strong>Make tile :</strong></h2>\n<p>First, reduce the size of the WSI by 0.33 times, and then divide it into 512*512 tiles. Subsequently, categorize these tiles into three levels based on the presence of bad pixels, identified by the condition \"np.sum(np.ptp(tile, axis=2) &lt; 20)\".<br>\nInference make tile code:</p>\n<pre><code> ():\n  path=\n  p_mask=\n  image=cv2.imread(path)\n  image=cv2.resize(image,(,),fx=scale,fy=scale,interpolation=cv2.INTER_AREA)\n  mask=np.load(p_mask)\n  os.makedirs(,exist_ok=)\n  count=\n   (count&lt;):\n      idxs=[(y,x)  y  (,image.shape[]//)  x  (,image.shape[]//)]\n      random.shuffle(idxs)\n       k, (y, x)  (idxs):\n          tile=image[y*:(y+)*,x*:(x+)*,:]\n          \n          bg_count=np.(np.ptp(tile,axis=)&lt;)\n           ((bg_count/(*))&lt;=):\n              cv2.imwrite(,tile)\n              count+=\n            count&gt;=: \n              \n       count&lt;:\n          idxs=[(y,x)  y  (,image.shape[]//)  x  (,image.shape[]//)]\n          random.shuffle(idxs)\n           k, (y, x)  (idxs):\n              tile=image[y*:(y+)*,x*:(x+)*,:]\n              \n              bg_count=np.(np.ptp(tile,axis=)&lt;)\n                ((bg_count/(*))&lt;=)&amp;((bg_count/(*))&gt;):\n                  cv2.imwrite(,tile)\n                  count+=\n                count&gt;=:\n                  \n       count&lt;:\n          idxs=[(y,x)  y  (,image.shape[]//)  x  (,image.shape[]//)]\n          random.shuffle(idxs)\n           k, (y, x)  (idxs):\n              tile=image[y*:(y+)*,x*:(x+)*,:]\n               \n              bg_count=np.(np.ptp(tile,axis=)&lt;)\n                ((bg_count/(*))&lt;=)&amp;((bg_count/(*))&gt;):\n                  cv2.imwrite(,tile)\n                  count+=\n                count&gt;=:\n                  \n</code></pre>\n<h4>Training tile:</h4>\n<p><strong>Step 1:</strong>Using all tiles if bg_count/area less 0.5<br>\n<strong>Step 2:</strong>If tiles of WSI image&lt;50,add ((bg_count/area) between 0.5-0.65)   tiles until there are 50 tiles.<br>\n<strong>Step 3:</strong>If tiles of WSI image&lt;20,add ((bg_count/area) between 0.65-0.75)  tiles until there are 20 tiles.</p>\n<h2>Model Training:</h2>\n<p>Only use WSI tiles. Randomly select 6 tiles from each image for training in every batch.<br>\nLoss Function: Binary Cross-Entropy (BCE)<br>\n<strong>Step 1:</strong> Normal Training<br>\n<strong>Step 2:</strong><br>\nUtilize the results from Step 1 to generate auxiliary labels.If the predicted value for true label is greater than 0.3, set the auxiliary label to 1; otherwise, set it to 0.<br>\nRe-train the model without using the weights from Step 1.<br>\nLoss function: Label loss (BCE) + 0.3 * Auxiliary Label loss (BCE)<br>\nLearning rate: 2e-4<br>\n<strong>Step 3:</strong> Fine-tuning with Step 2 Weights<br>\nFurther refine the model using the weights obtained from Step 2.<br>\nLoss function: Label loss (BCE) + 0.15 * Auxiliary Label loss (BCE)<br>\nLearning rate: 5e-5</p>\n<h4>Models with different backbone:</h4>\n<p>efficientnetb4,efficientnet_v2s,maxvit_tiny (The model settings of different backbones are slightly different.)</p>\n<h3>WSI</h3>\n<p>Use model to predict tiles.</p>\n<h3>Wsi tile ensemble:</h3>\n<pre><code>tile_df[]=np.(tile_df[[,,,,]],axis=)\ntile_df[]=np.argmax(tile_df[[,,,,]].values,axis=)\ntile_df=tile_df[[,,,]].groupby([,])[[,]].mean().reset_index()\nidx=tile_df.groupby([])[].idxmax()\nwsi_df=tile_df.loc[idx1].reset_index(drop=)\n</code></pre>\n<h3>Outliers(WSI):</h3>\n<p>The predicted mean value of aux_label&lt;0.5(The score is almost the same as not predict \"Other\",maybe+0.01)</p>\n<h2>tma:</h2>\n<p><strong>Step1.</strong>Crop tma</p>\n<pre><code> ():\n  ks=((img.shape[],img.shape[])//,)\n\n  mask=(img.(axis=)-img.(axis=))&gt;\n  kernel = np.ones((ks, ks),np.uint8)\n  mask=cv2.erode(mask.astype(np.uint8),kernel)\n  nonzero_pixels = np.column_stack(np.where(mask &gt; ))\n\n   (nonzero_pixels.size)&lt;(img.size//):\n       img\n  :\n\n      min_y, min_x = np.(nonzero_pixels, axis=)\n      max_y, max_x = np.(nonzero_pixels, axis=)\n        img[(,min_y-ks):max_y+ks+,(,min_x-ks):max_x+ks+,:]\n</code></pre>\n<p><strong>Step2.</strong>Resize to 512*512(The size of tma *0.33*0.5~512, so we can directly resize to 512 to predict)<br>\n<strong>Step3.</strong>Using wsi training model to predict</p>\n<h3>Outliers(tma):</h3>\n<p>The predicted value of aux_label &lt;0.5 (compared to tma without predict \"Other\", public score +0.03, private score +0.06)</p>\n<h2>Ensemble different models:</h2>\n<p>Voting(Compared with a single model, maybe only +0.01)</p>\n<h2>Maybe not work:</h2>\n<p>segmentation</p>",
      "rawMarkdown": "\n\n### Only use Competition Data, no External Data \n\n## **Split WSI and TMA:**\nWSI images have black pixels (all zeros in all three channels), while TMA images do not. Therefore, if both the image width and height are less than 6000, but the area of black pixels is greater than 5% of the image (all WSI images in the training data have more than 10% black pixels), it is classified as WSI; otherwise, it is classified as TMA.\n\n## **Make tile :**\n First, reduce the size of the WSI by 0.33 times, and then divide it into 512*512 tiles. Subsequently, categorize these tiles into three levels based on the presence of bad pixels, identified by the condition \"np.sum(np.ptp(tile, axis=2) < 20)\".\nInference make tile code:\n\n```python\ndef resize_image_and_make_tile(name,out_path,scale):\n    path=f\"/kaggle/input/UBC-OCEAN/{inference}_images/{name}.png\"\n    p_mask=f\"{pred_mask_512_folder}/{name}.npy\"\n    image=cv2.imread(path)\n    image=cv2.resize(image,(0,0),fx=scale,fy=scale,interpolation=cv2.INTER_AREA)\n    mask=np.load(p_mask)\n    os.makedirs(f\"{out_path}/{name}\",exist_ok=True)\n    count=0\n    if (count<20):\n        idxs=[(y,x) for y in range(0,image.shape[0]//512) for x in range(0,image.shape[1]//512)]\n        random.shuffle(idxs)\n        for k, (y, x) in enumerate(idxs):\n            tile=image[y*512:(y+1)*512,x*512:(x+1)*512,:]\n            #bg_count=np.sum((tile.max(axis=2)-tile.min(axis=2))<20)\n            bg_count=np.sum(np.ptp(tile,axis=2)<20)\n            if ((bg_count/(512*512))<=0.5):\n                cv2.imwrite(f\"{out_path}/{name}/{x}_{y}.png\",tile)\n                count+=1\n\n            if count>=60: #60\n                break\n        if count<20:\n            idxs=[(y,x) for y in range(0,image.shape[0]//512) for x in range(0,image.shape[1]//512)]\n            random.shuffle(idxs)\n            for k, (y, x) in enumerate(idxs):\n                tile=image[y*512:(y+1)*512,x*512:(x+1)*512,:]\n                #bg_count=np.sum((tile.max(axis=2)-tile.min(axis=2))<20)\n                bg_count=np.sum(np.ptp(tile,axis=2)<20)\n\n                if ((bg_count/(512*512))<=0.65)&((bg_count/(512*512))>0.5):\n                    cv2.imwrite(f\"{out_path}/{name}/{x}_{y}.png\",tile)\n                    count+=1\n\n                if count>=40:\n                    break\n        if count<10:\n            idxs=[(y,x) for y in range(0,image.shape[0]//512) for x in range(0,image.shape[1]//512)]\n            random.shuffle(idxs)\n            for k, (y, x) in enumerate(idxs):\n                tile=image[y*512:(y+1)*512,x*512:(x+1)*512,:]\n\n                #bg_count=np.sum((tile.max(axis=2)-tile.min(axis=2))<20)\n                bg_count=np.sum(np.ptp(tile,axis=2)<20)\n\n                if ((bg_count/(512*512))<=0.75)&((bg_count/(512*512))>0.65):\n                    cv2.imwrite(f\"{out_path}/{name}/{x}_{y}.png\",tile)\n                    count+=1\n\n                if count>=10:\n                    break\n```\n\n#### Training tile:\n\n**Step 1:**Using all tiles if bg_count/area less 0.5\n\n**Step 2:**If tiles of WSI image<50,add ((bg_count/area) between 0.5-0.65)   tiles until there are 50 tiles.\n\n**Step 3:**If tiles of WSI image<20,add ((bg_count/area) between 0.65-0.75)  tiles until there are 20 tiles.\n\n## Model Training:\n\nOnly use WSI tiles. Randomly select 6 tiles from each image for training in every batch.\nLoss Function: Binary Cross-Entropy (BCE)\n\n**Step 1:** Normal Training\n\n**Step 2:**\nUtilize the results from Step 1 to generate auxiliary labels.If the predicted value for true label is greater than 0.3, set the auxiliary label to 1; otherwise, set it to 0.\nRe-train the model without using the weights from Step 1.\nLoss function: Label loss (BCE) + 0.3 * Auxiliary Label loss (BCE)\nLearning rate: 2e-4\n\n**Step 3:** Fine-tuning with Step 2 Weights\nFurther refine the model using the weights obtained from Step 2.\nLoss function: Label loss (BCE) + 0.15 * Auxiliary Label loss (BCE)\nLearning rate: 5e-5\n\n#### Models with different backbone:\nefficientnetb4,efficientnet_v2s,maxvit_tiny (The model settings of different backbones are slightly different.)\n\n### WSI\nUse model to predict tiles.\n\n### Wsi tile ensemble:\n```python\ntile_df[\"prob\"]=np.max(tile_df[[\"pred_0\",\"pred_1\",\"pred_2\",\"pred_3\",\"pred_4\"]],axis=1)\ntile_df[\"pred\"]=np.argmax(tile_df[[\"pred_0\",\"pred_1\",\"pred_2\",\"pred_3\",\"pred_4\"]].values,axis=1)\ntile_df=tile_df[[\"image_id\",\"pred\",\"prob\",\"aux\"]].groupby([\"image_id\",\"pred\"])[[\"prob\",\"aux\"]].mean().reset_index()\nidx=tile_df.groupby([\"image_id\"])[\"prob\"].idxmax()\nwsi_df=tile_df.loc[idx1].reset_index(drop=True)\n```\n\n### Outliers(WSI):\n The predicted mean value of aux_label<0.5(The score is almost the same as not predict \"Other\",maybe+0.01)\n\n## tma:\n\n**Step1.**Crop tma\n```python\ndef crop_tma(img):\n    ks=min(min(img.shape[0],img.shape[1])//150,20)\n    \n    mask=(img.max(axis=2)-img.min(axis=2))>20\n    kernel = np.ones((ks, ks),np.uint8)\n    mask=cv2.erode(mask.astype(np.uint8),kernel)\n    nonzero_pixels = np.column_stack(np.where(mask > 0))\n    \n    if (nonzero_pixels.size)<(img.size//60):\n        return img\n    else:\n    \n        min_y, min_x = np.min(nonzero_pixels, axis=0)\n        max_y, max_x = np.max(nonzero_pixels, axis=0)\n\n        return img[max(0,min_y-ks):max_y+ks+1,max(0,min_x-ks):max_x+ks+1,:]\n```\n**Step2.**Resize to 512*512(The size of tma *0.33*0.5~512, so we can directly resize to 512 to predict)\n**Step3.**Using wsi training model to predict\n\n### Outliers(tma): \n The predicted value of aux_label <0.5 (compared to tma without predict \"Other\", public score +0.03, private score +0.06)\n\n## Ensemble different models:\nVoting(Compared with a single model, maybe only +0.01)\n\n## Maybe not work:\nsegmentation",
      "votes": 15
    },
    {
      "id": 2589038,
      "postDate": "2024-01-06T02:25:23.067Z",
      "content": "<p>Congratulations <a href=\"https://www.kaggle.com/chihantsai\" target=\"_blank\">@chihantsai</a> , we share some similar idea in out methods. It's good to see get a gold medal without MIL. I have a question. Did you just discard the 25 TMA in the training set?</p>",
      "rawMarkdown": "Congratulations @chihantsai , we share some similar idea in out methods. It's good to see get a gold medal without MIL. I have a question. Did you just discard the 25 TMA in the training set?",
      "replies": [
        {
          "id": 2589062,
          "postDate": "2024-01-06T03:12:20.673Z",
          "content": "<p>Yes,25 TMA not in the training.<br>\nIn testing, I can correctly predict 21-23 out of 25.<br>\nIn all 25 TMA cases, at least one of my three models predicted correctly, but I couldn't effectively ensemble it.</p>",
          "rawMarkdown": "Yes,25 TMA not in the training.\nIn testing, I can correctly predict 21-23 out of 25.\nIn all 25 TMA cases, at least one of my three models predicted correctly, but I couldn't effectively ensemble it.",
          "votes": 1,
          "replies": [
            {
              "id": 2589073,
              "postDate": "2024-01-06T03:30:50.667Z",
              "content": "<p>I also use them for validation only. 🤝</p>",
              "rawMarkdown": "I also use them for validation only. 🤝",
              "votes": 1
            }
          ]
        }
      ]
    }
  ],
  "comments": [
    {
      "id": 2589038,
      "author_name": "ForcewithMe",
      "author_url": "",
      "post_date": "2024-01-06T02:25:23.067000",
      "content": "<p>Congratulations <a href=\"https://www.kaggle.com/chihantsai\" target=\"_blank\">@chihantsai</a> , we share some similar idea in out methods. It's good to see get a gold medal without MIL. I have a question. Did you just discard the 25 TMA in the training set?</p>",
      "votes": 0,
      "replies": [
        {
          "id": 2589062,
          "author_name": "fate",
          "author_url": "",
          "post_date": "2024-01-06T03:12:20.673000",
          "content": "<p>Yes,25 TMA not in the training.<br>\nIn testing, I can correctly predict 21-23 out of 25.<br>\nIn all 25 TMA cases, at least one of my three models predicted correctly, but I couldn't effectively ensemble it.</p>",
          "votes": 1,
          "replies": [
            {
              "id": 2589073,
              "author_name": "ForcewithMe",
              "author_url": "",
              "post_date": "2024-01-06T03:30:50.667000",
              "content": "<p>I also use them for validation only. 🤝</p>",
              "votes": 1,
              "replies": []
            }
          ]
        }
      ]
    }
  ],
  "raw_markdown_by_id": {
    "2588774": "\n\n### Only use Competition Data, no External Data \n\n## **Split WSI and TMA:**\nWSI images have black pixels (all zeros in all three channels), while TMA images do not. Therefore, if both the image width and height are less than 6000, but the area of black pixels is greater than 5% of the image (all WSI images in the training data have more than 10% black pixels), it is classified as WSI; otherwise, it is classified as TMA.\n\n## **Make tile :**\n First, reduce the size of the WSI by 0.33 times, and then divide it into 512*512 tiles. Subsequently, categorize these tiles into three levels based on the presence of bad pixels, identified by the condition \"np.sum(np.ptp(tile, axis=2) < 20)\".\nInference make tile code:\n\n```python\ndef resize_image_and_make_tile(name,out_path,scale):\n    path=f\"/kaggle/input/UBC-OCEAN/{inference}_images/{name}.png\"\n    p_mask=f\"{pred_mask_512_folder}/{name}.npy\"\n    image=cv2.imread(path)\n    image=cv2.resize(image,(0,0),fx=scale,fy=scale,interpolation=cv2.INTER_AREA)\n    mask=np.load(p_mask)\n    os.makedirs(f\"{out_path}/{name}\",exist_ok=True)\n    count=0\n    if (count<20):\n        idxs=[(y,x) for y in range(0,image.shape[0]//512) for x in range(0,image.shape[1]//512)]\n        random.shuffle(idxs)\n        for k, (y, x) in enumerate(idxs):\n            tile=image[y*512:(y+1)*512,x*512:(x+1)*512,:]\n            #bg_count=np.sum((tile.max(axis=2)-tile.min(axis=2))<20)\n            bg_count=np.sum(np.ptp(tile,axis=2)<20)\n            if ((bg_count/(512*512))<=0.5):\n                cv2.imwrite(f\"{out_path}/{name}/{x}_{y}.png\",tile)\n                count+=1\n\n            if count>=60: #60\n                break\n        if count<20:\n            idxs=[(y,x) for y in range(0,image.shape[0]//512) for x in range(0,image.shape[1]//512)]\n            random.shuffle(idxs)\n            for k, (y, x) in enumerate(idxs):\n                tile=image[y*512:(y+1)*512,x*512:(x+1)*512,:]\n                #bg_count=np.sum((tile.max(axis=2)-tile.min(axis=2))<20)\n                bg_count=np.sum(np.ptp(tile,axis=2)<20)\n\n                if ((bg_count/(512*512))<=0.65)&((bg_count/(512*512))>0.5):\n                    cv2.imwrite(f\"{out_path}/{name}/{x}_{y}.png\",tile)\n                    count+=1\n\n                if count>=40:\n                    break\n        if count<10:\n            idxs=[(y,x) for y in range(0,image.shape[0]//512) for x in range(0,image.shape[1]//512)]\n            random.shuffle(idxs)\n            for k, (y, x) in enumerate(idxs):\n                tile=image[y*512:(y+1)*512,x*512:(x+1)*512,:]\n\n                #bg_count=np.sum((tile.max(axis=2)-tile.min(axis=2))<20)\n                bg_count=np.sum(np.ptp(tile,axis=2)<20)\n\n                if ((bg_count/(512*512))<=0.75)&((bg_count/(512*512))>0.65):\n                    cv2.imwrite(f\"{out_path}/{name}/{x}_{y}.png\",tile)\n                    count+=1\n\n                if count>=10:\n                    break\n```\n\n#### Training tile:\n\n**Step 1:**Using all tiles if bg_count/area less 0.5\n\n**Step 2:**If tiles of WSI image<50,add ((bg_count/area) between 0.5-0.65)   tiles until there are 50 tiles.\n\n**Step 3:**If tiles of WSI image<20,add ((bg_count/area) between 0.65-0.75)  tiles until there are 20 tiles.\n\n## Model Training:\n\nOnly use WSI tiles. Randomly select 6 tiles from each image for training in every batch.\nLoss Function: Binary Cross-Entropy (BCE)\n\n**Step 1:** Normal Training\n\n**Step 2:**\nUtilize the results from Step 1 to generate auxiliary labels.If the predicted value for true label is greater than 0.3, set the auxiliary label to 1; otherwise, set it to 0.\nRe-train the model without using the weights from Step 1.\nLoss function: Label loss (BCE) + 0.3 * Auxiliary Label loss (BCE)\nLearning rate: 2e-4\n\n**Step 3:** Fine-tuning with Step 2 Weights\nFurther refine the model using the weights obtained from Step 2.\nLoss function: Label loss (BCE) + 0.15 * Auxiliary Label loss (BCE)\nLearning rate: 5e-5\n\n#### Models with different backbone:\nefficientnetb4,efficientnet_v2s,maxvit_tiny (The model settings of different backbones are slightly different.)\n\n### WSI\nUse model to predict tiles.\n\n### Wsi tile ensemble:\n```python\ntile_df[\"prob\"]=np.max(tile_df[[\"pred_0\",\"pred_1\",\"pred_2\",\"pred_3\",\"pred_4\"]],axis=1)\ntile_df[\"pred\"]=np.argmax(tile_df[[\"pred_0\",\"pred_1\",\"pred_2\",\"pred_3\",\"pred_4\"]].values,axis=1)\ntile_df=tile_df[[\"image_id\",\"pred\",\"prob\",\"aux\"]].groupby([\"image_id\",\"pred\"])[[\"prob\",\"aux\"]].mean().reset_index()\nidx=tile_df.groupby([\"image_id\"])[\"prob\"].idxmax()\nwsi_df=tile_df.loc[idx1].reset_index(drop=True)\n```\n\n### Outliers(WSI):\n The predicted mean value of aux_label<0.5(The score is almost the same as not predict \"Other\",maybe+0.01)\n\n## tma:\n\n**Step1.**Crop tma\n```python\ndef crop_tma(img):\n    ks=min(min(img.shape[0],img.shape[1])//150,20)\n    \n    mask=(img.max(axis=2)-img.min(axis=2))>20\n    kernel = np.ones((ks, ks),np.uint8)\n    mask=cv2.erode(mask.astype(np.uint8),kernel)\n    nonzero_pixels = np.column_stack(np.where(mask > 0))\n    \n    if (nonzero_pixels.size)<(img.size//60):\n        return img\n    else:\n    \n        min_y, min_x = np.min(nonzero_pixels, axis=0)\n        max_y, max_x = np.max(nonzero_pixels, axis=0)\n\n        return img[max(0,min_y-ks):max_y+ks+1,max(0,min_x-ks):max_x+ks+1,:]\n```\n**Step2.**Resize to 512*512(The size of tma *0.33*0.5~512, so we can directly resize to 512 to predict)\n**Step3.**Using wsi training model to predict\n\n### Outliers(tma): \n The predicted value of aux_label <0.5 (compared to tma without predict \"Other\", public score +0.03, private score +0.06)\n\n## Ensemble different models:\nVoting(Compared with a single model, maybe only +0.01)\n\n## Maybe not work:\nsegmentation",
    "2589038": "Congratulations @chihantsai , we share some similar idea in out methods. It's good to see get a gold medal without MIL. I have a question. Did you just discard the 25 TMA in the training set?"
  }
}