{
  "id": 417536,
  "title": "3rd place solution",
  "url": "/competitions/vesuvius-challenge-ink-detection/writeups/wuyu-3rd-place-solution",
  "author_name": "",
  "post_date": "2023-07-13T09:25:35.393Z",
  "votes": 25,
  "comment_count": 3,
  "views": 0,
  "content": "<p>First of all, we want to thank the hosts who brought us a great opportunity to be a part of resurrecting an ancient library, which is really awesome! And congrats to all winners!</p>\n<h2>Summary</h2>\n<p>Our final solution chosen is a 15-unet-ensemble, which only use ir-CSN as 3d encoder with simple mean pooling bridging to 2d decoder. This idea refers to <a href=\"https://www.kaggle.com/samfc10\" target=\"_blank\">@samfc10</a>'s Notebook: <a href=\"https://www.kaggle.com/code/samfc10/vesuvius-challenge-3d-resnet-training\" target=\"_blank\">Vesuvius Challenge - 3D ResNet Training</a>. And our training and inference pipeline refer to <a href=\"https://www.kaggle.com/tanakar\" target=\"_blank\">@tanakar</a>'s great scalable sharing: <a href=\"https://www.kaggle.com/code/tanakar/2-5d-segmentaion-baseline-training/comments\" target=\"_blank\">2.5d segmentaion baseline</a>. Let me explain the details below.</p>\n<h2>Dataset</h2>\n<h3>Fold split</h3>\n<p>At first we just use the original 3 fragments for our local validation, and we got a very bad score on our first ensemble whose fold 2 scored the worst. So we thought it might caused by less training sample on fold 2.</p>\n<p>We split fragment 2 to 3 fragments referring to <a href=\"https://www.kaggle.com/junxhuang\" target=\"_blank\">@junxhuang</a>'s <a href=\"https://www.kaggle.com/competitions/vesuvius-challenge-ink-detection/discussion/398329#2201953\" target=\"_blank\">comment</a> and <a href=\"https://www.kaggle.com/tattaka\" target=\"_blank\">@tattaka</a>'s <a href=\"https://www.kaggle.com/competitions/vesuvius-challenge-ink-detection/discussion/401667\" target=\"_blank\">discussion</a>.</p>\n<p><img src=\"https://www.googleapis.com/download/storage/v1/b/kaggle-forum-message-attachments/o/inbox%2F9735064%2F7cbdc92edfa4491a4405db81528f99ce%2F1.png?generation=1686897867291657&amp;alt=media\" alt=\"\"></p>\n<p>And we found it interesting that our best public score  0.77 is a single ir-CSN-r50 unet trained on fold 2 with 4 rotation tta which gets 0.66 locally and 0.73 without tta. Of all our experiments, fold 2 always gets high score on public lb comparing to other folds.</p>\n<h3>Slice choose</h3>\n<p>We had struggled a long time on how many slices can bring us higher score. We chose slice following <a href=\"https://www.kaggle.com/tanakar\" target=\"_blank\">@tanakar</a>'s baseline, and experimented <code>8*i</code>slices with i select from 1, 2, 3, 4, 6. 24, 32 and 48 slices all gave us robust performance according to lb and cv. After seeing <a href=\"https://www.kaggle.com/pavelgonchar\" target=\"_blank\">@pavelgonchar</a>'s <a href=\"https://www.kaggle.com/competitions/vesuvius-challenge-ink-detection/discussion/403348#2235071\" target=\"_blank\">discussion</a>, we stay in 24 slices for most of our experiments.</p>\n<h3>Patch size</h3>\n<p>We spent almost 2/3 time in tuning model on 224x224 cropping size patches, because the smaller the size is the more training samples we can get. And cropping stride 224//2 gave us 13272 samples in total, which is sufficient to train a r50 type unet. The most robust single r50 type model trained on 224x224 cropping size and 224//2 cropping stride is the fold 5 scoring 0.74(with 4 rotation tta) on lb and 0.74 cv.</p>\n<p>In the last two weeks, after reading many brilliant winning solutions, e.g. <a href=\"https://www.kaggle.com/hesene\" target=\"_blank\">@hesene</a>'s <a href=\"https://www.kaggle.com/competitions/uw-madison-gi-tract-image-segmentation/discussion/337468\" target=\"_blank\">3rd place solution</a>, we decide to use bigger resolution and use multi-size cropping inference as our final submission. Because it is easy to get in mind that big cropping size can get the entire character for models. But there is a problem that the bigger patch we crop the little samples we get. When using a 864x864 size patch with 864//6 , our r152 type unet can easily overfit within 2 epochs. So we make some trade-off there, doing experiments on 500-700 size patch, and these sizes work for us. The best single big resolution model is 576x576 patch size r152 type unet on fold 5, scoring 0.77 locally.</p>\n<p><img src=\"https://www.googleapis.com/download/storage/v1/b/kaggle-forum-message-attachments/o/inbox%2F9735064%2F0598a60aad240ecc111899ca897d5975%2F2.png?generation=1686897881870354&amp;alt=media\" alt=\"\"></p>\n<h2>Models</h2>\n<h3>Architecture</h3>\n<p>Only unet is used for our segmentation models.</p>\n<h3>Backbone</h3>\n<p>In most of our experiments, we chose ir-CSN-r50 as encoder of our unets.  After reading <a href=\"https://www.kaggle.com/selimsef\" target=\"_blank\">@selimsef</a>'s <a href=\"https://www.kaggle.com/competitions/rsna-2022-cervical-spine-fracture-detection/discussion/364837\" target=\"_blank\">4th place solution</a> and <a href=\"https://www.kaggle.com/nvnnghia\" target=\"_blank\">@nvnnghia</a>'s <a href=\"https://www.kaggle.com/competitions/nfl-player-contact-detection/discussion/391635\" target=\"_blank\">1st place solution</a>, we stick to openmmlab's great implementation and pretrained weights.</p>\n<h3>Decoder</h3>\n<p>This is our first time participating a segmentation competition. At the very beginning, we spent so much time to build a better decoder, but after reading some solutions, we found that the channels of decoder is the most unconcerned part. From the experiments, light weight decoders perform good enough, so all our unets' decoder channel start from 256. Our decoder code is using <a href=\"https://github.com/selimsef/xview3_solution\" target=\"_blank\">selimsef's xview3_solution</a>, and we made some modifications.</p>\n<h3>Backbone-data align</h3>\n<p>At first we gave model with 1 channel voxel. We need to sum the first conv pretrained weights to 1 channel like <a href=\"https://www.kaggle.com/samfc10\" target=\"_blank\">@samfc10</a> did.</p>\n<p><img src=\"https://www.googleapis.com/download/storage/v1/b/kaggle-forum-message-attachments/o/inbox%2F9735064%2Fe683a7577522d400d937e953a738270f%2F3.png?generation=1686897894991869&amp;alt=media\" alt=\"\"></p>\n<p>But after reading <a href=\"https://www.kaggle.com/pelegshilo\" target=\"_blank\">@pelegshilo</a>'s <a href=\"https://www.kaggle.com/competitions/vesuvius-challenge-ink-detection/discussion/395676#2196236\" target=\"_blank\">comment</a> and <a href=\"https://www.kaggle.com/hengck23\" target=\"_blank\">‎‎‎‎‎‎‎‎@hengck23</a>'s great <a href=\"https://www.kaggle.com/competitions/vesuvius-challenge-ink-detection/discussion/407972#2302894\" target=\"_blank\">topic</a>, we thought maybe we can make a full use of pretrained weights of first conv and let model choose the best z location. We simply do concatenation from temporal dimension to channel dimension with 3 overlapped voxels.</p>\n<p><img src=\"https://www.googleapis.com/download/storage/v1/b/kaggle-forum-message-attachments/o/inbox%2F9735064%2Fd1c1046526fca219835eaff4e0bdb42e%2F4.png?generation=1686897907062423&amp;alt=media\" alt=\"\"></p>\n<p>In our experiments, input voxel size (28,224,224) with overlapped start indices [0,2,4] which give 3 24-slice voxels, boosting cv from 0.01 to 0.03 on different folds.</p>\n<h2>Training settings</h2>\n<h3>Data correlated</h3>\n<ul>\n<li><p>We use <a href=\"https://www.kaggle.com/code/tanakar/2-5d-segmentaion-baseline-training/comments\" target=\"_blank\">2.5d segmentaion baseline</a> provided augmentations and change the probabilities for different experiment. </p></li>\n<li><p>We set the ShiftScaleRotate's rotate_limit to 180 referring to <a href=\"https://www.kaggle.com/hengck23\" target=\"_blank\">‎‎‎‎‎‎‎‎@hengck23</a>'s <a href=\"https://www.kaggle.com/competitions/vesuvius-challenge-ink-detection/discussion/407972#2302894\" target=\"_blank\">topic</a>.</p></li>\n<li><p>Using mixup and cutmix boost cv differently on each fold and with different mix probabilities. We comment out cutout when use cutmix.</p></li>\n<li><p>Segmenting on normalized pixels works, referring to <a href=\"https://www.kaggle.com/yoyobar\" target=\"_blank\">@yoyobar's</a> <a href=\"https://www.kaggle.com/code/yoyobar/3d-resnet-baseline-inference\" target=\"_blank\">sharing</a>.</p></li>\n<li><p>We use clean input meaning we filtered out all zero input.</p></li>\n</ul>\n<h3>Model correlated</h3>\n<ul>\n<li><p>We use adamw optimizer and onecycle scheduler.</p></li>\n<li><p>We use BCE and hard dice with different weighted average in our experiments.</p></li>\n<li><p>We use EMA and it boosts cv sometimes.</p></li>\n<li><p>DDP training and validation is used when training models on big resolution patches.</p></li>\n<li><p>Gradient accumulation is used when training models on big resolution patches. We use batch size 16 in all our experiments.</p></li>\n</ul>\n<h2>Inference</h2>\n<ul>\n<li>We use smaller cropping stride comparing to which we use in training.</li>\n<li>Multi-size cropping inference ensemble help us survive in such shrinking</li>\n<li>We do not select threshold for each model prediction. We just use 0.5.</li>\n</ul>\n<p>Our final solution is a 224-384-576-15-unet ensemble with no tta which scoring 0.76 on public. In this setting we use 28-24 slice selection that means slice file id range from 18 to 46 and copying it to 3 24-slice voxels with slice start index [0,2,4]. For 576 size model we scale up to r152 and the rest are r50. This give us 3rd place on private.</p>\n<p>We find that rotation tta do not boost much when add some big resolution models.</p>\n<p>But we have 6 different ensemble settings higher than 0.682 private score. The best one scoring 0.687873 on private with 0.749338 on public. In this solution we add 5 more r152 unets with training input size 640 which has 36-32 slice with start index [0,2,4]. We didn't choose it because the low lb and some overfitting performance between train loss and validation loss.<br>\nHere we list two of them.</p>\n<table>\n<thead>\n<tr>\n<th>ensemble models</th>\n<th>input size</th>\n<th>number of slices</th>\n<th>cropping stride</th>\n<th>public score</th>\n<th>private score</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td>r50,r50,r152</td>\n<td>224,384,576</td>\n<td>28-24,28-24,28-24</td>\n<td>224//8,384//6,576//8</td>\n<td>0.763384</td>\n<td>0.681137</td>\n</tr>\n<tr>\n<td>r50,r50,r152,r152</td>\n<td>224,384,576,640</td>\n<td>28-24,28-24,28-24, 36-32</td>\n<td>224//2,384//3,576//4,640//5</td>\n<td>0.749338</td>\n<td>0.687873</td>\n</tr>\n<tr>\n<td>r152</td>\n<td>576</td>\n<td>28-24</td>\n<td>576//12</td>\n<td>0.720028</td>\n<td>0.683273</td>\n</tr>\n</tbody>\n</table>\n<h2>Tried but didn't work</h2>\n<ul>\n<li>2d models</li>\n<li>pure 3d models</li>\n<li>stacked unet which use 2d denoiser</li>\n<li>training a 3d denoiser</li>\n<li>classification branch for stronger supervision</li>\n<li>add maxpooling and conv layers between encoder and decoder</li>\n</ul>\n<h2>Acknowledgement</h2>\n<p>We want to thank all those kagglers we mentioned above. We have learned a lot from your sharing, and there is still a lot we need to learn.</p>\n<p>We also want to thank the kaggle community, everyone here is free to share their insight that is really helpful for one's growing up.</p>\n<p>Again, Thanks to the hosts who bring such great competition! We have seen a lot of brilliant teams with wonderful solution.</p>\n<p>This is my teammate <a href=\"https://www.kaggle.com/yufujiang\" target=\"_blank\">@yufujiang</a>'s  first time participating in a kaggle competition who is also my roommate at college, many thanks to him. And this is my 3rd time competition on kaggle, finally I got my first medal!</p>\n<h2>Code</h2>\n<p><a href=\"https://www.kaggle.com/code/traptinblur/3rd-place-ensemble-576-8-384-6-224-8/notebook\" target=\"_blank\">3rd place inference code</a><br>\n<a href=\"https://github.com/traptinblur/VCID_2023_3rd_place_code/tree/main\" target=\"_blank\">3rd place training code repo</a></p>",
  "messages": [
    {
      "id": "2304658",
      "postDate": "06/16/2023 06:36:05",
      "content": "<p>First of all, we want to thank the hosts who brought us a great opportunity to be a part of resurrecting an ancient library, which is really awesome! And congrats to all winners!</p>\n<h2>Summary</h2>\n<p>Our final solution chosen is a 15-unet-ensemble, which only use ir-CSN as 3d encoder with simple mean pooling bridging to 2d decoder. This idea refers to <a href=\"https://www.kaggle.com/samfc10\" target=\"_blank\">@samfc10</a>'s Notebook: <a href=\"https://www.kaggle.com/code/samfc10/vesuvius-challenge-3d-resnet-training\" target=\"_blank\">Vesuvius Challenge - 3D ResNet Training</a>. And our training and inference pipeline refer to <a href=\"https://www.kaggle.com/tanakar\" target=\"_blank\">@tanakar</a>'s great scalable sharing: <a href=\"https://www.kaggle.com/code/tanakar/2-5d-segmentaion-baseline-training/comments\" target=\"_blank\">2.5d segmentaion baseline</a>. Let me explain the details below.</p>\n<h2>Dataset</h2>\n<h3>Fold split</h3>\n<p>At first we just use the original 3 fragments for our local validation, and we got a very bad score on our first ensemble whose fold 2 scored the worst. So we thought it might caused by less training sample on fold 2.</p>\n<p>We split fragment 2 to 3 fragments referring to <a href=\"https://www.kaggle.com/junxhuang\" target=\"_blank\">@junxhuang</a>'s <a href=\"https://www.kaggle.com/competitions/vesuvius-challenge-ink-detection/discussion/398329#2201953\" target=\"_blank\">comment</a> and <a href=\"https://www.kaggle.com/tattaka\" target=\"_blank\">@tattaka</a>'s <a href=\"https://www.kaggle.com/competitions/vesuvius-challenge-ink-detection/discussion/401667\" target=\"_blank\">discussion</a>.</p>\n<p><img src=\"https://www.googleapis.com/download/storage/v1/b/kaggle-forum-message-attachments/o/inbox%2F9735064%2F7cbdc92edfa4491a4405db81528f99ce%2F1.png?generation=1686897867291657&amp;alt=media\" alt=\"\"></p>\n<p>And we found it interesting that our best public score  0.77 is a single ir-CSN-r50 unet trained on fold 2 with 4 rotation tta which gets 0.66 locally and 0.73 without tta. Of all our experiments, fold 2 always gets high score on public lb comparing to other folds.</p>\n<h3>Slice choose</h3>\n<p>We had struggled a long time on how many slices can bring us higher score. We chose slice following <a href=\"https://www.kaggle.com/tanakar\" target=\"_blank\">@tanakar</a>'s baseline, and experimented <code>8*i</code>slices with i select from 1, 2, 3, 4, 6. 24, 32 and 48 slices all gave us robust performance according to lb and cv. After seeing <a href=\"https://www.kaggle.com/pavelgonchar\" target=\"_blank\">@pavelgonchar</a>'s <a href=\"https://www.kaggle.com/competitions/vesuvius-challenge-ink-detection/discussion/403348#2235071\" target=\"_blank\">discussion</a>, we stay in 24 slices for most of our experiments.</p>\n<h3>Patch size</h3>\n<p>We spent almost 2/3 time in tuning model on 224x224 cropping size patches, because the smaller the size is the more training samples we can get. And cropping stride 224//2 gave us 13272 samples in total, which is sufficient to train a r50 type unet. The most robust single r50 type model trained on 224x224 cropping size and 224//2 cropping stride is the fold 5 scoring 0.74(with 4 rotation tta) on lb and 0.74 cv.</p>\n<p>In the last two weeks, after reading many brilliant winning solutions, e.g. <a href=\"https://www.kaggle.com/hesene\" target=\"_blank\">@hesene</a>'s <a href=\"https://www.kaggle.com/competitions/uw-madison-gi-tract-image-segmentation/discussion/337468\" target=\"_blank\">3rd place solution</a>, we decide to use bigger resolution and use multi-size cropping inference as our final submission. Because it is easy to get in mind that big cropping size can get the entire character for models. But there is a problem that the bigger patch we crop the little samples we get. When using a 864x864 size patch with 864//6 , our r152 type unet can easily overfit within 2 epochs. So we make some trade-off there, doing experiments on 500-700 size patch, and these sizes work for us. The best single big resolution model is 576x576 patch size r152 type unet on fold 5, scoring 0.77 locally.</p>\n<p><img src=\"https://www.googleapis.com/download/storage/v1/b/kaggle-forum-message-attachments/o/inbox%2F9735064%2F0598a60aad240ecc111899ca897d5975%2F2.png?generation=1686897881870354&amp;alt=media\" alt=\"\"></p>\n<h2>Models</h2>\n<h3>Architecture</h3>\n<p>Only unet is used for our segmentation models.</p>\n<h3>Backbone</h3>\n<p>In most of our experiments, we chose ir-CSN-r50 as encoder of our unets.  After reading <a href=\"https://www.kaggle.com/selimsef\" target=\"_blank\">@selimsef</a>'s <a href=\"https://www.kaggle.com/competitions/rsna-2022-cervical-spine-fracture-detection/discussion/364837\" target=\"_blank\">4th place solution</a> and <a href=\"https://www.kaggle.com/nvnnghia\" target=\"_blank\">@nvnnghia</a>'s <a href=\"https://www.kaggle.com/competitions/nfl-player-contact-detection/discussion/391635\" target=\"_blank\">1st place solution</a>, we stick to openmmlab's great implementation and pretrained weights.</p>\n<h3>Decoder</h3>\n<p>This is our first time participating a segmentation competition. At the very beginning, we spent so much time to build a better decoder, but after reading some solutions, we found that the channels of decoder is the most unconcerned part. From the experiments, light weight decoders perform good enough, so all our unets' decoder channel start from 256. Our decoder code is using <a href=\"https://github.com/selimsef/xview3_solution\" target=\"_blank\">selimsef's xview3_solution</a>, and we made some modifications.</p>\n<h3>Backbone-data align</h3>\n<p>At first we gave model with 1 channel voxel. We need to sum the first conv pretrained weights to 1 channel like <a href=\"https://www.kaggle.com/samfc10\" target=\"_blank\">@samfc10</a> did.</p>\n<p><img src=\"https://www.googleapis.com/download/storage/v1/b/kaggle-forum-message-attachments/o/inbox%2F9735064%2Fe683a7577522d400d937e953a738270f%2F3.png?generation=1686897894991869&amp;alt=media\" alt=\"\"></p>\n<p>But after reading <a href=\"https://www.kaggle.com/pelegshilo\" target=\"_blank\">@pelegshilo</a>'s <a href=\"https://www.kaggle.com/competitions/vesuvius-challenge-ink-detection/discussion/395676#2196236\" target=\"_blank\">comment</a> and <a href=\"https://www.kaggle.com/hengck23\" target=\"_blank\">‎‎‎‎‎‎‎‎@hengck23</a>'s great <a href=\"https://www.kaggle.com/competitions/vesuvius-challenge-ink-detection/discussion/407972#2302894\" target=\"_blank\">topic</a>, we thought maybe we can make a full use of pretrained weights of first conv and let model choose the best z location. We simply do concatenation from temporal dimension to channel dimension with 3 overlapped voxels.</p>\n<p><img src=\"https://www.googleapis.com/download/storage/v1/b/kaggle-forum-message-attachments/o/inbox%2F9735064%2Fd1c1046526fca219835eaff4e0bdb42e%2F4.png?generation=1686897907062423&amp;alt=media\" alt=\"\"></p>\n<p>In our experiments, input voxel size (28,224,224) with overlapped start indices [0,2,4] which give 3 24-slice voxels, boosting cv from 0.01 to 0.03 on different folds.</p>\n<h2>Training settings</h2>\n<h3>Data correlated</h3>\n<ul>\n<li><p>We use <a href=\"https://www.kaggle.com/code/tanakar/2-5d-segmentaion-baseline-training/comments\" target=\"_blank\">2.5d segmentaion baseline</a> provided augmentations and change the probabilities for different experiment. </p></li>\n<li><p>We set the ShiftScaleRotate's rotate_limit to 180 referring to <a href=\"https://www.kaggle.com/hengck23\" target=\"_blank\">‎‎‎‎‎‎‎‎@hengck23</a>'s <a href=\"https://www.kaggle.com/competitions/vesuvius-challenge-ink-detection/discussion/407972#2302894\" target=\"_blank\">topic</a>.</p></li>\n<li><p>Using mixup and cutmix boost cv differently on each fold and with different mix probabilities. We comment out cutout when use cutmix.</p></li>\n<li><p>Segmenting on normalized pixels works, referring to <a href=\"https://www.kaggle.com/yoyobar\" target=\"_blank\">@yoyobar's</a> <a href=\"https://www.kaggle.com/code/yoyobar/3d-resnet-baseline-inference\" target=\"_blank\">sharing</a>.</p></li>\n<li><p>We use clean input meaning we filtered out all zero input.</p></li>\n</ul>\n<h3>Model correlated</h3>\n<ul>\n<li><p>We use adamw optimizer and onecycle scheduler.</p></li>\n<li><p>We use BCE and hard dice with different weighted average in our experiments.</p></li>\n<li><p>We use EMA and it boosts cv sometimes.</p></li>\n<li><p>DDP training and validation is used when training models on big resolution patches.</p></li>\n<li><p>Gradient accumulation is used when training models on big resolution patches. We use batch size 16 in all our experiments.</p></li>\n</ul>\n<h2>Inference</h2>\n<ul>\n<li>We use smaller cropping stride comparing to which we use in training.</li>\n<li>Multi-size cropping inference ensemble help us survive in such shrinking</li>\n<li>We do not select threshold for each model prediction. We just use 0.5.</li>\n</ul>\n<p>Our final solution is a 224-384-576-15-unet ensemble with no tta which scoring 0.76 on public. In this setting we use 28-24 slice selection that means slice file id range from 18 to 46 and copying it to 3 24-slice voxels with slice start index [0,2,4]. For 576 size model we scale up to r152 and the rest are r50. This give us 3rd place on private.</p>\n<p>We find that rotation tta do not boost much when add some big resolution models.</p>\n<p>But we have 6 different ensemble settings higher than 0.682 private score. The best one scoring 0.687873 on private with 0.749338 on public. In this solution we add 5 more r152 unets with training input size 640 which has 36-32 slice with start index [0,2,4]. We didn't choose it because the low lb and some overfitting performance between train loss and validation loss.<br>\nHere we list two of them.</p>\n<table>\n<thead>\n<tr>\n<th>ensemble models</th>\n<th>input size</th>\n<th>number of slices</th>\n<th>cropping stride</th>\n<th>public score</th>\n<th>private score</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td>r50,r50,r152</td>\n<td>224,384,576</td>\n<td>28-24,28-24,28-24</td>\n<td>224//8,384//6,576//8</td>\n<td>0.763384</td>\n<td>0.681137</td>\n</tr>\n<tr>\n<td>r50,r50,r152,r152</td>\n<td>224,384,576,640</td>\n<td>28-24,28-24,28-24, 36-32</td>\n<td>224//2,384//3,576//4,640//5</td>\n<td>0.749338</td>\n<td>0.687873</td>\n</tr>\n<tr>\n<td>r152</td>\n<td>576</td>\n<td>28-24</td>\n<td>576//12</td>\n<td>0.720028</td>\n<td>0.683273</td>\n</tr>\n</tbody>\n</table>\n<h2>Tried but didn't work</h2>\n<ul>\n<li>2d models</li>\n<li>pure 3d models</li>\n<li>stacked unet which use 2d denoiser</li>\n<li>training a 3d denoiser</li>\n<li>classification branch for stronger supervision</li>\n<li>add maxpooling and conv layers between encoder and decoder</li>\n</ul>\n<h2>Acknowledgement</h2>\n<p>We want to thank all those kagglers we mentioned above. We have learned a lot from your sharing, and there is still a lot we need to learn.</p>\n<p>We also want to thank the kaggle community, everyone here is free to share their insight that is really helpful for one's growing up.</p>\n<p>Again, Thanks to the hosts who bring such great competition! We have seen a lot of brilliant teams with wonderful solution.</p>\n<p>This is my teammate <a href=\"https://www.kaggle.com/yufujiang\" target=\"_blank\">@yufujiang</a>'s  first time participating in a kaggle competition who is also my roommate at college, many thanks to him. And this is my 3rd time competition on kaggle, finally I got my first medal!</p>\n<h2>Code</h2>\n<p><a href=\"https://www.kaggle.com/code/traptinblur/3rd-place-ensemble-576-8-384-6-224-8/notebook\" target=\"_blank\">3rd place inference code</a><br>\n<a href=\"https://github.com/traptinblur/VCID_2023_3rd_place_code/tree/main\" target=\"_blank\">3rd place training code repo</a></p>",
      "rawMarkdown": "First of all, we want to thank the hosts who brought us a great opportunity to be a part of resurrecting an ancient library, which is really awesome! And congrats to all winners!\n\n## Summary\n\nOur final solution chosen is a 15-unet-ensemble, which only use ir-CSN as 3d encoder with simple mean pooling bridging to 2d decoder. This idea refers to [@samfc10](https://www.kaggle.com/samfc10)'s Notebook: [Vesuvius Challenge - 3D ResNet Training](https://www.kaggle.com/code/samfc10/vesuvius-challenge-3d-resnet-training). And our training and inference pipeline refer to [@tanakar](https://www.kaggle.com/tanakar)'s great scalable sharing: [2.5d segmentaion baseline](https://www.kaggle.com/code/tanakar/2-5d-segmentaion-baseline-training/comments). Let me explain the details below.\n\n## Dataset\n\n### Fold split\n\nAt first we just use the original 3 fragments for our local validation, and we got a very bad score on our first ensemble whose fold 2 scored the worst. So we thought it might caused by less training sample on fold 2.\n\nWe split fragment 2 to 3 fragments referring to [@junxhuang](https://www.kaggle.com/junxhuang)'s [comment](https://www.kaggle.com/competitions/vesuvius-challenge-ink-detection/discussion/398329#2201953) and [@tattaka](https://www.kaggle.com/tattaka)'s [discussion](https://www.kaggle.com/competitions/vesuvius-challenge-ink-detection/discussion/401667).\n\n![](https://www.googleapis.com/download/storage/v1/b/kaggle-forum-message-attachments/o/inbox%2F9735064%2F7cbdc92edfa4491a4405db81528f99ce%2F1.png?generation=1686897867291657&alt=media =430x548)\n\nAnd we found it interesting that our best public score  0.77 is a single ir-CSN-r50 unet trained on fold 2 with 4 rotation tta which gets 0.66 locally and 0.73 without tta. Of all our experiments, fold 2 always gets high score on public lb comparing to other folds.\n\n### Slice choose\n\nWe had struggled a long time on how many slices can bring us higher score. We chose slice following [@tanakar](https://www.kaggle.com/tanakar)'s baseline, and experimented `8*i `slices with i select from 1, 2, 3, 4, 6. 24, 32 and 48 slices all gave us robust performance according to lb and cv. After seeing [@pavelgonchar](https://www.kaggle.com/pavelgonchar)'s [discussion](https://www.kaggle.com/competitions/vesuvius-challenge-ink-detection/discussion/403348#2235071), we stay in 24 slices for most of our experiments.\n\n### Patch size\n\nWe spent almost 2/3 time in tuning model on 224x224 cropping size patches, because the smaller the size is the more training samples we can get. And cropping stride 224//2 gave us 13272 samples in total, which is sufficient to train a r50 type unet. The most robust single r50 type model trained on 224x224 cropping size and 224//2 cropping stride is the fold 5 scoring 0.74(with 4 rotation tta) on lb and 0.74 cv.\n\nIn the last two weeks, after reading many brilliant winning solutions, e.g. [@hesene](https://www.kaggle.com/hesene)'s [3rd place solution](https://www.kaggle.com/competitions/uw-madison-gi-tract-image-segmentation/discussion/337468), we decide to use bigger resolution and use multi-size cropping inference as our final submission. Because it is easy to get in mind that big cropping size can get the entire character for models. But there is a problem that the bigger patch we crop the little samples we get. When using a 864x864 size patch with 864//6 , our r152 type unet can easily overfit within 2 epochs. So we make some trade-off there, doing experiments on 500-700 size patch, and these sizes work for us. The best single big resolution model is 576x576 patch size r152 type unet on fold 5, scoring 0.77 locally.\n\n![](https://www.googleapis.com/download/storage/v1/b/kaggle-forum-message-attachments/o/inbox%2F9735064%2F0598a60aad240ecc111899ca897d5975%2F2.png?generation=1686897881870354&alt=media =503x372)\n\n## Models\n\n### Architecture\n\nOnly unet is used for our segmentation models.\n\n### Backbone\n\nIn most of our experiments, we chose ir-CSN-r50 as encoder of our unets.  After reading [@selimsef](https://www.kaggle.com/selimsef)'s [4th place solution](https://www.kaggle.com/competitions/rsna-2022-cervical-spine-fracture-detection/discussion/364837) and [@nvnnghia](https://www.kaggle.com/nvnnghia)'s [1st place solution](https://www.kaggle.com/competitions/nfl-player-contact-detection/discussion/391635), we stick to openmmlab's great implementation and pretrained weights.\n\n### Decoder\n\nThis is our first time participating a segmentation competition. At the very beginning, we spent so much time to build a better decoder, but after reading some solutions, we found that the channels of decoder is the most unconcerned part. From the experiments, light weight decoders perform good enough, so all our unets' decoder channel start from 256. Our decoder code is using [selimsef's xview3_solution](https://github.com/selimsef/xview3_solution), and we made some modifications.\n\n### Backbone-data align\n\nAt first we gave model with 1 channel voxel. We need to sum the first conv pretrained weights to 1 channel like [@samfc10](https://www.kaggle.com/samfc10) did.\n\n![](https://www.googleapis.com/download/storage/v1/b/kaggle-forum-message-attachments/o/inbox%2F9735064%2Fe683a7577522d400d937e953a738270f%2F3.png?generation=1686897894991869&alt=media =840x512)\n\nBut after reading [@pelegshilo](https://www.kaggle.com/pelegshilo)'s [comment](https://www.kaggle.com/competitions/vesuvius-challenge-ink-detection/discussion/395676#2196236) and [‎‎‎‎‎‎‎‎@hengck23](https://www.kaggle.com/hengck23)'s great [topic](https://www.kaggle.com/competitions/vesuvius-challenge-ink-detection/discussion/407972#2302894), we thought maybe we can make a full use of pretrained weights of first conv and let model choose the best z location. We simply do concatenation from temporal dimension to channel dimension with 3 overlapped voxels.\n\n![](https://www.googleapis.com/download/storage/v1/b/kaggle-forum-message-attachments/o/inbox%2F9735064%2Fd1c1046526fca219835eaff4e0bdb42e%2F4.png?generation=1686897907062423&alt=media =840x512)\n\nIn our experiments, input voxel size (28,224,224) with overlapped start indices [0,2,4] which give 3 24-slice voxels, boosting cv from 0.01 to 0.03 on different folds.\n\n## Training settings\n\n### Data correlated\n\n* We use [2.5d segmentaion baseline](https://www.kaggle.com/code/tanakar/2-5d-segmentaion-baseline-training/comments) provided augmentations and change the probabilities for different experiment. \n* We set the ShiftScaleRotate's rotate_limit to 180 referring to [‎‎‎‎‎‎‎‎@hengck23](https://www.kaggle.com/hengck23)'s [topic](https://www.kaggle.com/competitions/vesuvius-challenge-ink-detection/discussion/407972#2302894).\n\n* Using mixup and cutmix boost cv differently on each fold and with different mix probabilities. We comment out cutout when use cutmix.\n\n* Segmenting on normalized pixels works, referring to [@yoyobar's](https://www.kaggle.com/yoyobar) [sharing](https://www.kaggle.com/code/yoyobar/3d-resnet-baseline-inference).\n\n* We use clean input meaning we filtered out all zero input.\n\n### Model correlated\n\n* We use adamw optimizer and onecycle scheduler.\n* We use BCE and hard dice with different weighted average in our experiments.\n\n* We use EMA and it boosts cv sometimes.\n\n* DDP training and validation is used when training models on big resolution patches.\n* Gradient accumulation is used when training models on big resolution patches. We use batch size 16 in all our experiments.\n\n## Inference\n\n* We use smaller cropping stride comparing to which we use in training.\n* Multi-size cropping inference ensemble help us survive in such shrinking\n* We do not select threshold for each model prediction. We just use 0.5.\n\nOur final solution is a 224-384-576-15-unet ensemble with no tta which scoring 0.76 on public. In this setting we use 28-24 slice selection that means slice file id range from 18 to 46 and copying it to 3 24-slice voxels with slice start index [0,2,4]. For 576 size model we scale up to r152 and the rest are r50. This give us 3rd place on private.\n\nWe find that rotation tta do not boost much when add some big resolution models.\n\nBut we have 6 different ensemble settings higher than 0.682 private score. The best one scoring 0.687873 on private with 0.749338 on public. In this solution we add 5 more r152 unets with training input size 640 which has 36-32 slice with start index [0,2,4]. We didn't choose it because the low lb and some overfitting performance between train loss and validation loss.\nHere we list two of them.\n\n| ensemble models   | input size      | number of slices         | cropping stride             | public score | private score |\n| ----------------- | --------------- | ------------------------ | --------------------------- | ------------ | ------------- |\n| r50,r50,r152      | 224,384,576     | 28-24,28-24,28-24        | 224//8,384//6,576//8        | 0.763384     | 0.681137      |\n| r50,r50,r152,r152 | 224,384,576,640 | 28-24,28-24,28-24, 36-32 | 224//2,384//3,576//4,640//5 | 0.749338     | 0.687873      |\n| r152              | 576             | 28-24                    | 576//12                     | 0.720028     | 0.683273      |\n\n## Tried but didn't work\n\n* 2d models\n* pure 3d models\n* stacked unet which use 2d denoiser\n* training a 3d denoiser\n* classification branch for stronger supervision\n* add maxpooling and conv layers between encoder and decoder\n\n## Acknowledgement\n\nWe want to thank all those kagglers we mentioned above. We have learned a lot from your sharing, and there is still a lot we need to learn.\n\nWe also want to thank the kaggle community, everyone here is free to share their insight that is really helpful for one's growing up.\n\nAgain, Thanks to the hosts who bring such great competition! We have seen a lot of brilliant teams with wonderful solution.\n\nThis is my teammate [@yufujiang](https://www.kaggle.com/yufujiang)'s  first time participating in a kaggle competition who is also my roommate at college, many thanks to him. And this is my 3rd time competition on kaggle, finally I got my first medal!\n## Code\n[3rd place inference code](https://www.kaggle.com/code/traptinblur/3rd-place-ensemble-576-8-384-6-224-8/notebook)\n[3rd place training code repo](https://github.com/traptinblur/VCID_2023_3rd_place_code/tree/main)",
      "votes": null
    },
    {
      "id": "2304901",
      "postDate": "06/16/2023 09:59:23",
      "content": "<p>Congratulations! Extensive and meticulous work, this gold medal is well-deserved! </p>",
      "rawMarkdown": "Congratulations! Extensive and meticulous work, this gold medal is well-deserved!",
      "votes": null
    },
    {
      "id": "2311746",
      "postDate": "06/21/2023 12:32:37",
      "content": "<p>Congratulations! I'm a journalist with Scientific American. I'd love to talk about your approach. Could we arrange a video call? email: tomasjweber@gmail.com. Thanks!</p>",
      "rawMarkdown": "Congratulations! I'm a journalist with Scientific American. I'd love to talk about your approach. Could we arrange a video call? email: tomasjweber@gmail.com. Thanks!",
      "votes": null
    },
    {
      "id": "2325604",
      "postDate": "07/01/2023 13:14:48",
      "content": "<p>Our training code is released <a href=\"https://github.com/traptinblur/VCID_2023_3rd_place_code/tree/main\" target=\"_blank\">here</a>.<br>\nAnd we release our checkpoints for <a href=\"https://www.kaggle.com/code/traptinblur/3rd-place-ensemble-576-8-384-6-224-8/notebook\" target=\"_blank\">inference notebook</a>.</p>",
      "rawMarkdown": "Our training code is released [here](https://github.com/traptinblur/VCID_2023_3rd_place_code/tree/main).\nAnd we release our checkpoints for [inference notebook](https://www.kaggle.com/code/traptinblur/3rd-place-ensemble-576-8-384-6-224-8/notebook).",
      "votes": null
    }
  ],
  "comments": [
    {
      "id": 2304901,
      "author_name": "chenlin1999",
      "author_url": "",
      "post_date": "06/16/2023 09:59:23",
      "content": "<p>Congratulations! Extensive and meticulous work, this gold medal is well-deserved! </p>",
      "votes": null,
      "replies": []
    },
    {
      "id": 2311746,
      "author_name": "tomasjweber",
      "author_url": "",
      "post_date": "06/21/2023 12:32:37",
      "content": "<p>Congratulations! I'm a journalist with Scientific American. I'd love to talk about your approach. Could we arrange a video call? email: tomasjweber@gmail.com. Thanks!</p>",
      "votes": null,
      "replies": []
    },
    {
      "id": 2325604,
      "author_name": "traptinblur",
      "author_url": "",
      "post_date": "07/01/2023 13:14:48",
      "content": "<p>Our training code is released <a href=\"https://github.com/traptinblur/VCID_2023_3rd_place_code/tree/main\" target=\"_blank\">here</a>.<br>\nAnd we release our checkpoints for <a href=\"https://www.kaggle.com/code/traptinblur/3rd-place-ensemble-576-8-384-6-224-8/notebook\" target=\"_blank\">inference notebook</a>.</p>",
      "votes": null,
      "replies": []
    }
  ],
  "raw_markdown_by_id": {
    "2304658": "First of all, we want to thank the hosts who brought us a great opportunity to be a part of resurrecting an ancient library, which is really awesome! And congrats to all winners!\n\n## Summary\n\nOur final solution chosen is a 15-unet-ensemble, which only use ir-CSN as 3d encoder with simple mean pooling bridging to 2d decoder. This idea refers to [@samfc10](https://www.kaggle.com/samfc10)'s Notebook: [Vesuvius Challenge - 3D ResNet Training](https://www.kaggle.com/code/samfc10/vesuvius-challenge-3d-resnet-training). And our training and inference pipeline refer to [@tanakar](https://www.kaggle.com/tanakar)'s great scalable sharing: [2.5d segmentaion baseline](https://www.kaggle.com/code/tanakar/2-5d-segmentaion-baseline-training/comments). Let me explain the details below.\n\n## Dataset\n\n### Fold split\n\nAt first we just use the original 3 fragments for our local validation, and we got a very bad score on our first ensemble whose fold 2 scored the worst. So we thought it might caused by less training sample on fold 2.\n\nWe split fragment 2 to 3 fragments referring to [@junxhuang](https://www.kaggle.com/junxhuang)'s [comment](https://www.kaggle.com/competitions/vesuvius-challenge-ink-detection/discussion/398329#2201953) and [@tattaka](https://www.kaggle.com/tattaka)'s [discussion](https://www.kaggle.com/competitions/vesuvius-challenge-ink-detection/discussion/401667).\n\n![](https://www.googleapis.com/download/storage/v1/b/kaggle-forum-message-attachments/o/inbox%2F9735064%2F7cbdc92edfa4491a4405db81528f99ce%2F1.png?generation=1686897867291657&alt=media =430x548)\n\nAnd we found it interesting that our best public score  0.77 is a single ir-CSN-r50 unet trained on fold 2 with 4 rotation tta which gets 0.66 locally and 0.73 without tta. Of all our experiments, fold 2 always gets high score on public lb comparing to other folds.\n\n### Slice choose\n\nWe had struggled a long time on how many slices can bring us higher score. We chose slice following [@tanakar](https://www.kaggle.com/tanakar)'s baseline, and experimented `8*i `slices with i select from 1, 2, 3, 4, 6. 24, 32 and 48 slices all gave us robust performance according to lb and cv. After seeing [@pavelgonchar](https://www.kaggle.com/pavelgonchar)'s [discussion](https://www.kaggle.com/competitions/vesuvius-challenge-ink-detection/discussion/403348#2235071), we stay in 24 slices for most of our experiments.\n\n### Patch size\n\nWe spent almost 2/3 time in tuning model on 224x224 cropping size patches, because the smaller the size is the more training samples we can get. And cropping stride 224//2 gave us 13272 samples in total, which is sufficient to train a r50 type unet. The most robust single r50 type model trained on 224x224 cropping size and 224//2 cropping stride is the fold 5 scoring 0.74(with 4 rotation tta) on lb and 0.74 cv.\n\nIn the last two weeks, after reading many brilliant winning solutions, e.g. [@hesene](https://www.kaggle.com/hesene)'s [3rd place solution](https://www.kaggle.com/competitions/uw-madison-gi-tract-image-segmentation/discussion/337468), we decide to use bigger resolution and use multi-size cropping inference as our final submission. Because it is easy to get in mind that big cropping size can get the entire character for models. But there is a problem that the bigger patch we crop the little samples we get. When using a 864x864 size patch with 864//6 , our r152 type unet can easily overfit within 2 epochs. So we make some trade-off there, doing experiments on 500-700 size patch, and these sizes work for us. The best single big resolution model is 576x576 patch size r152 type unet on fold 5, scoring 0.77 locally.\n\n![](https://www.googleapis.com/download/storage/v1/b/kaggle-forum-message-attachments/o/inbox%2F9735064%2F0598a60aad240ecc111899ca897d5975%2F2.png?generation=1686897881870354&alt=media =503x372)\n\n## Models\n\n### Architecture\n\nOnly unet is used for our segmentation models.\n\n### Backbone\n\nIn most of our experiments, we chose ir-CSN-r50 as encoder of our unets.  After reading [@selimsef](https://www.kaggle.com/selimsef)'s [4th place solution](https://www.kaggle.com/competitions/rsna-2022-cervical-spine-fracture-detection/discussion/364837) and [@nvnnghia](https://www.kaggle.com/nvnnghia)'s [1st place solution](https://www.kaggle.com/competitions/nfl-player-contact-detection/discussion/391635), we stick to openmmlab's great implementation and pretrained weights.\n\n### Decoder\n\nThis is our first time participating a segmentation competition. At the very beginning, we spent so much time to build a better decoder, but after reading some solutions, we found that the channels of decoder is the most unconcerned part. From the experiments, light weight decoders perform good enough, so all our unets' decoder channel start from 256. Our decoder code is using [selimsef's xview3_solution](https://github.com/selimsef/xview3_solution), and we made some modifications.\n\n### Backbone-data align\n\nAt first we gave model with 1 channel voxel. We need to sum the first conv pretrained weights to 1 channel like [@samfc10](https://www.kaggle.com/samfc10) did.\n\n![](https://www.googleapis.com/download/storage/v1/b/kaggle-forum-message-attachments/o/inbox%2F9735064%2Fe683a7577522d400d937e953a738270f%2F3.png?generation=1686897894991869&alt=media =840x512)\n\nBut after reading [@pelegshilo](https://www.kaggle.com/pelegshilo)'s [comment](https://www.kaggle.com/competitions/vesuvius-challenge-ink-detection/discussion/395676#2196236) and [‎‎‎‎‎‎‎‎@hengck23](https://www.kaggle.com/hengck23)'s great [topic](https://www.kaggle.com/competitions/vesuvius-challenge-ink-detection/discussion/407972#2302894), we thought maybe we can make a full use of pretrained weights of first conv and let model choose the best z location. We simply do concatenation from temporal dimension to channel dimension with 3 overlapped voxels.\n\n![](https://www.googleapis.com/download/storage/v1/b/kaggle-forum-message-attachments/o/inbox%2F9735064%2Fd1c1046526fca219835eaff4e0bdb42e%2F4.png?generation=1686897907062423&alt=media =840x512)\n\nIn our experiments, input voxel size (28,224,224) with overlapped start indices [0,2,4] which give 3 24-slice voxels, boosting cv from 0.01 to 0.03 on different folds.\n\n## Training settings\n\n### Data correlated\n\n* We use [2.5d segmentaion baseline](https://www.kaggle.com/code/tanakar/2-5d-segmentaion-baseline-training/comments) provided augmentations and change the probabilities for different experiment. \n* We set the ShiftScaleRotate's rotate_limit to 180 referring to [‎‎‎‎‎‎‎‎@hengck23](https://www.kaggle.com/hengck23)'s [topic](https://www.kaggle.com/competitions/vesuvius-challenge-ink-detection/discussion/407972#2302894).\n\n* Using mixup and cutmix boost cv differently on each fold and with different mix probabilities. We comment out cutout when use cutmix.\n\n* Segmenting on normalized pixels works, referring to [@yoyobar's](https://www.kaggle.com/yoyobar) [sharing](https://www.kaggle.com/code/yoyobar/3d-resnet-baseline-inference).\n\n* We use clean input meaning we filtered out all zero input.\n\n### Model correlated\n\n* We use adamw optimizer and onecycle scheduler.\n* We use BCE and hard dice with different weighted average in our experiments.\n\n* We use EMA and it boosts cv sometimes.\n\n* DDP training and validation is used when training models on big resolution patches.\n* Gradient accumulation is used when training models on big resolution patches. We use batch size 16 in all our experiments.\n\n## Inference\n\n* We use smaller cropping stride comparing to which we use in training.\n* Multi-size cropping inference ensemble help us survive in such shrinking\n* We do not select threshold for each model prediction. We just use 0.5.\n\nOur final solution is a 224-384-576-15-unet ensemble with no tta which scoring 0.76 on public. In this setting we use 28-24 slice selection that means slice file id range from 18 to 46 and copying it to 3 24-slice voxels with slice start index [0,2,4]. For 576 size model we scale up to r152 and the rest are r50. This give us 3rd place on private.\n\nWe find that rotation tta do not boost much when add some big resolution models.\n\nBut we have 6 different ensemble settings higher than 0.682 private score. The best one scoring 0.687873 on private with 0.749338 on public. In this solution we add 5 more r152 unets with training input size 640 which has 36-32 slice with start index [0,2,4]. We didn't choose it because the low lb and some overfitting performance between train loss and validation loss.\nHere we list two of them.\n\n| ensemble models   | input size      | number of slices         | cropping stride             | public score | private score |\n| ----------------- | --------------- | ------------------------ | --------------------------- | ------------ | ------------- |\n| r50,r50,r152      | 224,384,576     | 28-24,28-24,28-24        | 224//8,384//6,576//8        | 0.763384     | 0.681137      |\n| r50,r50,r152,r152 | 224,384,576,640 | 28-24,28-24,28-24, 36-32 | 224//2,384//3,576//4,640//5 | 0.749338     | 0.687873      |\n| r152              | 576             | 28-24                    | 576//12                     | 0.720028     | 0.683273      |\n\n## Tried but didn't work\n\n* 2d models\n* pure 3d models\n* stacked unet which use 2d denoiser\n* training a 3d denoiser\n* classification branch for stronger supervision\n* add maxpooling and conv layers between encoder and decoder\n\n## Acknowledgement\n\nWe want to thank all those kagglers we mentioned above. We have learned a lot from your sharing, and there is still a lot we need to learn.\n\nWe also want to thank the kaggle community, everyone here is free to share their insight that is really helpful for one's growing up.\n\nAgain, Thanks to the hosts who bring such great competition! We have seen a lot of brilliant teams with wonderful solution.\n\nThis is my teammate [@yufujiang](https://www.kaggle.com/yufujiang)'s  first time participating in a kaggle competition who is also my roommate at college, many thanks to him. And this is my 3rd time competition on kaggle, finally I got my first medal!\n## Code\n[3rd place inference code](https://www.kaggle.com/code/traptinblur/3rd-place-ensemble-576-8-384-6-224-8/notebook)\n[3rd place training code repo](https://github.com/traptinblur/VCID_2023_3rd_place_code/tree/main)",
    "2304901": "Congratulations! Extensive and meticulous work, this gold medal is well-deserved!",
    "2311746": "Congratulations! I'm a journalist with Scientific American. I'd love to talk about your approach. Could we arrange a video call? email: tomasjweber@gmail.com. Thanks!",
    "2325604": "Our training code is released [here](https://github.com/traptinblur/VCID_2023_3rd_place_code/tree/main).\nAnd we release our checkpoints for [inference notebook](https://www.kaggle.com/code/traptinblur/3rd-place-ensemble-576-8-384-6-224-8/notebook)."
  },
  "source": "meta"
}