[
  {
    "path": ".gitignore",
    "content": ".idea\n__pycache__\n*.sh\nmisc.py\n"
  },
  {
    "path": "LICENSE",
    "content": "MIT License\n\nCopyright (c) 2020 Tivadar Danka\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
  },
  {
    "path": "README.md",
    "content": "# pytorch-UNet\n\nA tunable implementation of [U-Net](https://arxiv.org/abs/1505.04597) in PyTorch.\n\n- [About U-Net](#unet)\n- [U-Net quickstart](#quickstart)\n  - [Training](#training)\n  - [Predicting](#predicting)\n- [Customizing the network](#customizing)\n  - [The UNet2D object](#unet2d)\n- [Utilities for training the model](#utilities)\n  - [Wrapper for training and inference](#wrapper)\n  - [Datasets and augmentation transforms](#dataset)\n- [Experiments with U-Net](#experiments)\n  - [The Kaggle Data Science Bowl 2018 nuclei detection challenge dataset](#dataset)\n\n## About U-Net<a name=\"unet\"></a>\n\n<p align=\"middle\">\n  <img src=\"https://lmb.informatik.uni-freiburg.de/people/ronneber/u-net/u-net-architecture.png\" width=\"500\" align=\"middle\"/>\n</p>  \n[U-Net](https://arxiv.org/abs/1505.04597) is a powerful encoder-decoder CNN architecture for semantic segmentation, developed by Olaf Ronneberger, \nPhilipp Fischer and Thomas Brox. It has won several competitions, for example the ISBI Cell Tracking Challenge 2015 or\nthe Kaggle Data Science Bowl 2018.   \n\nAn example image from the Kaggle Data Science Bowl 2018:\n<p align=\"middle\">\n  <img src=\"docs/img/tissue_original.png\" width=\"256\" />\n  <img src=\"docs/img/tissue_segmented.png\" width=\"256\" />\n</p>\n\nThis repository was created to \n1. provide a reference implementation of 2D and 3D U-Net in PyTorch,\n2. allow fast prototyping and hyperparameter tuning by providing an easily parametrizable model. \n\nIn essence, the U-Net is built up using encoder and decoder blocks, each of them consisting of convolutional\nand pooling layers. With this implementation, you can build your U-Net using the `First`, `Encoder`, `Center`,\n`Decoder` and `Last` blocks, controlling the complexity and the number of these blocks. \n(Because the first, last and the middle of these blocks are somewhat special, they require their own class.)  \n\n**WARNING!** The 3D U-Net implementation is currently untested!  \n\n## U-Net quickstart<a name=\"quickstart\"></a>\n\nThe simplest way to use the implemented U-Net is with the provided `train.py` and `predict.py` scripts.  \n\n### Training<a name=\"training\"></a>\nFor training, `train.py` should be used, where the required arguments are\n- `--train_dataset`: path to the training dataset which should be structured like\n```\nimages_folder\n   |-- images\n       |-- img001.png\n       |-- img002.png\n       |-- ...\n   |-- masks\n       |-- img001.png\n       |-- img002.png\n       |-- ...\n```\n- `--checkpoint_path`: path to the folder where you wish to save the results (the trained model, predictions\nfor images in the validation set and log of losses and metrics during training).\n\nOptional arguments:\n- `--val_dataset`: path to the validation dataset, having the same structure as the training\ndataset indicated above. Defaults to None.\n- `--device`: the device where you wish to perform training and inference. Possible values are 'cpu',\n'cuda:0', 'cuda:1', etc. Defaults for 'cpu'.\n- `--in_channels`: the number of channels in your images. Defaults to 3.\n- `--out_channels`: the number of classes in your image. Defaults to 2.\n- `--depth`: controls the depth of the network. Defaults to 5. (For detailed explanation, see\n[Customizing the network](#customizing).)\n- `--width`: the complexity of each block. More width = more filters learned per layer. Defaults to 32. (For detailed explanation, see\n[Customizing the network](#customizing).)\n- `--epochs`: number of epochs during training. Defaults to 100.\n- `--batch_size`: the size of the minibatch during each training loop. Defaults to 1. You should tune this to\ncompletely fill the memory of your GPU.\n- `--save_freq`: the frequency of saving the model and predictions. Defaults to 0, which results in not saving\nthe model at all, only the logs.\n- `--save_model`: 1 if you want to save the model at every `save_freq` epochs and 0 if you dont. Defaults to 0. \n- `--model_name`: name of the model. Defaults to `model`. (Useful for parameter tuning.)\n- `--learning_rate`: the learning rate for training. Defaults to 1e-3.\n- `--crop`: integer describing the size of random crops taken from training and validation images.\nDefaults to None, which results in no cropping. For example, if set to 256, the model is trained and\nvaldiated on (256, 256) sized random crops. \n\n### Predicting<a name=\"predicting\"></a>\nFor prediction, the `predict.py` script should be used, where the required arguments are\n- `--dataset`: path to the dataset for which you would like to save the predictions.\n- `--results_path`: path to the folder where you wish to save the images.\n- `--model_path`: path to the saved model which you would like to use for inference.\nOptional arguments:\n- `--device`: the device where you wish to perform training and inference. Possible values are 'cpu',\n'cuda:0', 'cuda:1', etc. Defaults for 'cpu'.\n\n## Customizing the network<a name=\"customizing\"></a>\nAs you can see on [this figure](https://lmb.informatik.uni-freiburg.de/people/ronneber/u-net/u-net-architecture.png),\nthe U-Net architecture is basically made from convolution blocks. In the original architecture, the flow\nlooks like  \n<p align=\"middle\">\n  1 → 64 → 128 → 256 → 512 → 1024 (channels)<br>\n  1024 → 512 → 256 → 128 → 64 → 1 (channels).\n</p>  \nThis is quite arbitrary and it might not be the best architecture for your problem. With the implementation\nprovided in this repository, this can be changed quickly without requiring you to tweak the code, as you'll\nsee in the next section.\n\n### The UNet2D object<a name=\"unet2d\"></a>\nThe 2D [U-Net](https://arxiv.org/abs/1505.04597) architecture is implemented by the `unet.unet.UNet2D`\nclass. It accepts the following arguments during initialization:\n- `in_channels`: the number of channels in your images. (Required)\n- `out_channels`: the number of classes in your images. (Required)\n- `conv_depths`: a list describing the number of filters learned by the consecutive convolutional blocks.\nFor example, the original architecture outlined above can be described as `[64, 128, 256, 512, 1024]`.\nThe argument defaults to this structure.\n\n## Utilities for training the model<a name=\"utilities\"></a>\nTo save time with writing the usual boilerplate PyTorch code for training, a dataset generator and a\nsimple wrapper is provided.  \n\n### Wrapper for training and inference<a name=\"wrapper\"></a>\nThe wrapper is implemented in the `unet.model.Model` object. Upon initialization, you are required to\nprovide the following arguments:\n- `net`: PyTorch model.\n- `loss`: loss function which you would like to use during training.\n- `optimizer`: optimizer for the training.\n- `checkpoint_folder`: folder for saving the results and predictions.\n\nOptional arguments are:\n- `scheduler`: learning rate scheduler for the optimizer.\n- `device`: The device on which the model and tensor should be located. The default device is the cpu.\n\nTo train the model, the `.fit_dataset()` method can be used. For details on how to use it, see its docstring.\nTo do this, you'll need to use the `unet.dataset.ImageToImage2D` dataset generator, which is described in the\nnext section. \n\n### Datasets and augmentation transforms<a name=\"dataset\">\nFor training the U-Net, simple classes for augmentations and dataset input is implemented. The joint\naugmentation transform for image and mask is implemented in `unet.dataset.JointTransform2D`. This transform is\nused by the `unet.dataset.ImageToImage2D`. For more details on their usage, see their corresponding docstrings.  \n\n## Experiments with U-Net<a name=\"experiments\"></a>\nTo get a good grip on U-Net and how it depends on hyperparameters, I have made a simple experiment using the\ndataset from the Kaggle Data Science Bowl 2018, which aims to find cell nuclei in microscopy images. Although\nthe goal of the competition was instance based segmentation which is not exactly the proper use of U-Net, it\nactually won the race with some really clever tricks. (For details, see \n[this post by the winner team](https://www.kaggle.com/c/data-science-bowl-2018/discussion/54741), explaining\nwhat they did in detail.)  \n\nFor simplicity, the following experiments are focused on a simplified problem: segmenting out nuclei from the\nbackground, disregarding the differences between instances of nuclei.   \n\n### The Kaggle Data Science Bowl 2018 nuclei detection challenge dataset<a name=\"dataset\"></a>\nIf you would like to play around with the data, you can\n[download the images from here](https://www.kaggle.com/c/data-science-bowl-2018/data). Since the ground truth\nmasks are given for each instance, we need some preprocessing. This can be done with the provided script\n`kaggle_dsb18_preprocessing.py`, in the `kaggle_dsb18` folder. It requires two arguments:\n- `--dataset_path`: path to the downloaded dataset.\n- `--export_path`: path to the folder where you wish to save the results.\n\nThe images in this dataset can be subdivided further: fluorescent images, brightfield images and histopathological\nimages containing tissue. If you also want to make this split, you can find the corresponding image names\nin the `kaggle_dsb18` folder.   \n\n"
  },
  {
    "path": "kaggle_dsb18/brightfield.txt",
    "content": "08275a5b1c2dfcd739e8c4888a5ee2d29f83eccfa75185404ced1dc0866ea992\n091944f1d2611c916b98c020bd066667e33f4639159b2a92407fe5a40788856d\n1a11552569160f0b1ea10bedbd628ce6c14f29edec5092034c2309c556df833e\n1b44d22643830cd4f23c9deadb0bd499fb392fb2cd9526d81547d93077d983df\n2a1a294e21d76efd0399e4eb321b45f44f7510911acd92c988480195c5b4c812\n3594684b9ea0e16196f498815508f8d364d55fea2933a2e782122b6f00375d04\n4217e25defac94ff465157d53f5a24b8a14045b763d8606ec4a97d71d99ee381\n4e07a653352b30bb95b60ebc6c57afbc7215716224af731c51ff8d430788cd40\n54793624413c7d0e048173f7aeee85de3277f7e8d47c82e0a854fe43e879cd12\n5d58600efa0c2667ec85595bf456a54e2bd6e6e9a5c0dff42d807bc9fe2b822e\n5e263abff938acba1c0cff698261c7c00c23d7376e3ceacc3d5d4a655216b16d\n76a372bfd3fad3ea30cb163b560e52607a8281f5b042484c3a0fc6d0aa5a7450\n7f38885521586fc6011bef1314a9fb2aa1e4935bd581b2991e1d963395eab770\n8d05fb18ee0cda107d56735cafa6197a31884e0a5092dc6d41760fb92ae23ab4\n8f94a80b95a881d0efdec36affc915dca9609f4cba8134c4a91b219d418778aa\nc395870ad9f5a3ae651b50efab9b20c3e6b9aea15d4c731eb34c0cf9e3800a72\n"
  },
  {
    "path": "kaggle_dsb18/fluorescent.txt",
    "content": "00071198d059ba7f5914a526d124d28e6d010c92466da21d4a04cd5413362552\n003cee89357d9fe13516167fd67b609a164651b21934585648c740d2c3d86dc1\n0280fa8f60f6bcae0f97d93c28f60be194f9309ff610dc5845e60455b0f87c21\n0287e7ee5b007c91ae2bd7628d09735e70496bc6127ecb7f3dd043e04ce37426\n02903040e19ddf92f452907644ad3822918f54af41dd85e5a3fe3e1b6d6f9339\n03398329ced0c23b9ac3fac84dd53a87d9ffe4d9d10f1b5fe8df8fac12380776\n03b9306f44e9b8951461623dcbd615550cdcf36ea93b203f2c8fa58ed1dffcbe\n03f583ec5018739f4abb9b3b4a580ac43bd933c4337ad8877aa18b1dfb59fc9a\n0402a81e75262469925ea893b6706183832e85324f7b1e08e634129f5d522cdd\n04acab7636c4cf61d288a5962f15fa456b7bde31a021e5deedfbf51288e4001e\n05040e2e959c3f5632558fc9683fec88f0010026c555b499066346f67fdd0e13\n0532c64c2fd0c4d3188cc751cdfd566b1cfba3d269358717295bab1504c7c275\n05a8f65ebd0b30d3b210f30b4d640c847c2e710d0d135e0aeeaccbe1988e3b6e\n06350c7cc618be442c15706db7a68e91f313758d224de4608f9b960106d4f9ca\n06c779330d6d3447be21df2b9f05d1088f5b3b50dc48724fc130b1fd2896a68c\n072ff14c1d3245bf49ad6f1d4c71cdb18f1cb78a8e06fd2f53767e28f727cb81\n07761fa39f60dc37022dbbe8d8694595fd5b77ceb2af2a2724768c8e524d6770\n077f026f4ab0f0bcc0856644d99cbf639e443ec4f067d7b708bc6cecac609424\n07fb37aafa6626608af90c1e18f6a743f29b6b233d2e427dcd1102df6a916cf5\n08151b19806eebd58e5acec7e138dbfbb1761f41a1ab9620466584ecc7d5fada\n08ae2741df2f5ac815c0f272a8c532b5167ee853be9b939b9b8b7fa93560868a\n094afe36759e7daffe12188ab5987581d405b06720f1d5acf3f2614f404df380\n0a7d30b252359a10fd298b638b90cb9ada3acced4e0c0e5a3692013f432ee4e9\n0acd2c223d300ea55d0546797713851e818e5c697d073b7f4091b96ce0f3d2fe\n0b0d577159f0d6c266f360f7b8dfde46e16fa665138bf577ec3c6f9c70c0cd1e\n0b2e702f90aee4fff2bc6e4326308d50cf04701082e718d4f831c8959fbcda93\n0bda515e370294ed94efd36bd53782288acacb040c171df2ed97fd691fc9d8fe\n0bf4b144167694b6846d584cf52c458f34f28fcae75328a2a096c8214e01c0d0\n0c6507d493bf79b2ba248c5cca3d14df8b67328b89efa5f4a32f97a06a88c92c\n0d2bf916cc8de90d02f4cd4c23ea79b227dbc45d845b4124ffea380c92d34c8c\n0ddd8deaf1696db68b00c600601c6a74a0502caaf274222c8367bdc31458ae7e\n0e5edb072788c7b1da8829b02a49ba25668b09f7201cf2b70b111fc3b853d14f\n0ea221716cf13710214dcd331a61cea48308c3940df1d28cfc7fd817c83714e1\n1023509cf8d4c155467800f89508690be9513431992f470594281cd37dbd020d\n10328b822b836e67b547b4144e0b7eb43747c114ce4cacd8b540648892945b00\n10ba6cbee4873b32d5626a118a339832ba2b15d8643f66dddcd7cb2ec80fbc28\n11a0170f44e3ab4a8d669ae8ea9546d3a32ebfe6486d9066e5648d30b4e1cb69\n12aeefb1b522b283819b12e4cfaf6b13c1264c0aadac3412b4edd2ace304cb40\n12f89395ad5d21491ab9cec137e247652451d283064773507d7dc362243c5b8e\n139946af9e2c7ef4f0298e622b831dbef5e5c0cd088eb5bc3382f8df9355443d\n13c8ff1f49886e91c98ce795c93648ad8634c782ff57eb928ce29496b0425057\n1400420310c9094361a8a243545187f1d4c2365e081b3bb08c5fa29c7491a55b\n14cc1424c59808274e123db51292e9dbb5b037ef3e7c767a8c45c9ac733b91bf\n150b0ffa318c87b31d78af0e87d60390dbcd84b5f228a8c1fb3225cbe5df3e3f\n1609b1b8480ee52652a644403b3f7d5511410a016750aa3b9a4c8ddb3e893e8e\n16c3d5935ba94b720becc24b7a05741c26149e221e3401924080f41e2f891368\n1740b0a67ca337ea31648b57c81bcfbb841c7bb5cad185199a9f4da596d531b9\n175dbb364bfefc9537931144861c9b6e08934df3992782c669c6fe4234319dfc\n1815cf307859b3e13669041d181aa3b3dbbac1a95aef4c42164b223110c09168\n193ffaa5272d5c421ae02130a64d98ad120ec70e4ed97a72cdcd4801ce93b066\n19f0653c33982a416feed56e5d1ce6849fd83314fd19dfa1c5b23c6b66e9868a\n1a75de9e11303142864efed27e69ea1960dbd82ca910de221a777ed2caf35a6b\n1a75e9f15481d11084fe66bc2a5afac6dc5bec20ed56a7351a6d65ef0fe8762b\n1b2bf5933b0fb82918d278983bee66e9532b53807c3638efd9af66d20a2bae88\n1b518cd2ea84a389c267662840f3d902d0129fab27696215db2488de6d4316c5\n1b6044e4858a9b7cee9b0028d8e54fbc8fb72e6c4424ab5b9f3859bfc72b33c5\n1bd0f2b3000b7c7723f25335fabfcdddcdf4595dd7de1b142d52bb7a186885f0\n1c2f9e121fc207efff79d46390df1a740566b683ff56a96d8cabe830a398dd2e\n1c681dfa5cf7e413305d2e90ee47553a46e29cce4f6ed034c8297e511714f867\n1c8b905c9519061d6d091e702b45274f4485c80dcf7fb1491e6b2723f5002180\n1d4a5e729bb96b08370789cad0791f6e52ce0ffe1fcc97a04046420b43c851dd\n1d5f4717e179a03675a5aac3fc1c862fb442ddc3e373923016fd6b1430da889b\n1db1cddf28e305c9478519cfac144eee2242183fe59061f1f15487e925e8f5b5\n1e61ecf354cb93a62a9561db87a53985fb54e001444f98112ed0fc623fad793e\n1e8408fbb1619e7a0bcdd0bcd21fae57e7cb1f297d4c79787a9d0f5695d77073\n1ee4a111f0e0bb9b001121b94ff98ca736fad03797b25285fe33a47046b3e4b0\n1f0008060150b5b93084ae2e4dabd160ab80a95ce8071a321b80ec4e33b58aca\n1f6b7cead15344593b32d5f2345fc26713dc74d9b31306c824209d67da401fd8\n1f9e429c12f4477221b5b855a5f494fda2ef6d064ff75b061ffaf093e91758c5\n20468e8779c43e089dc0ff30f25e6cf3872d5aa6a0fdad6f8aca382da43e8582\n20b20ab049372d184c705acebe7af026d3580f5fd5a72ed796e3622e1685af2f\n20c37b1ad2f510ed7396969e855fe93d0d05611738f6e706e8ca1d1aed3ded45\n20e209f6ffa120a72712e1b4c1d3e24d1339227e2936abd4bbd49a636fada423\n212b858a66f0d23768b8e3e1357704fc2f4cf4bbe7eed8cd59b5d01031d553e6\n21408476af0506331e8b5d49b385833e5ef1fbb90815fbf9af9d19b4bb145f76\n220b37f4ca7cab486d2b71cd87a46ee7411a5aa142799d96ed98015ab5ba538a\n2227fd9b01d67c2bcdb407d3205214e6dfeff9fd0725828e3b3651959942ff4a\n2349e95ece2857c89db7e4a8be8c88af0b45f3c4262608120cb3bd6ef51fd241\n237802ac5005f9cf782367156c46c383efd9e05088e5768ca883cbbe24abadb1\n23830d0e51245fc0c9e410efa4c17d2a7d83a0104a3777130119ab892de47a4e\n243443ae303cc09cfbea85bfd22b0c4f026342f3dfc3aa1076f27867910d025b\n245b995878370ef4ea977568b2b67f93d4ecaa9308761b9d3e148e0803780183\n27c30f9011492f234e4587c9a4b53c787037d486f658821196fe354240ac3c47\n2817299fd3b88670e86a9db5651ba24333c299d1d41e5491aabfcd95aee84174\n2869fad54664677e81bacbf00c2256e89a7b90b69d9688c9342e2c736ff5421c\n28d33efef218392e79e385906deb88055d94b65ad217de78c07e85476f80f45a\n295ac4ecf2ee0211c065cf5dbb93b1eb8e61347153447209cd110e9c3e355e81\n29780b28e6a75fac7b96f164a1580666513199794f1b19a5df8587fe0cb59b67\n29dd28df98ee51b4ab1a87f5509538ecc3e4697fc57c40c6165658f61b0d8e3a\n29ea4f6eb4545f43868a9b40a60000426bf8dfd9d062546656a37bd2a2aaf9ec\n2a2032c4ed78f3fc64de7e5efd0bec26a81680b07404eaa54a1744b7ab3f8365\n2ab91a4408860ae8339689ed9f87aa9359de1bdd4ca5c2eab7fff7724dbd6707\n2abc40c118bc7303592c8bb95a80361e27560854b8971ab34dcf91966575b1f2\n2ad489c11ed8b77a9d8a2339ac64ffc38e79281c03a2507db4688fd3186c0fe5\n2b50b1e3fa5c5aa39bc84ebfaea9961b7199c4d2488ae0b48d0b3459807d59d2\n2bf594e9d06f78b4b79d7ffb395497a0a91126b6b0d710d7a9cee21f5c3bd177\n2c83c86dd4e5dacc024b55629375567fb8e320a82ef86f541cfe54764040fc25\n2c840a94d216f5ef4e499b53ae885e9b022cbf639e004ec788436093837823b2\n2cfa61bef6542dd359717e9131ce6f076c415a3bd7f48cb093b0d7f3b2ca785d\n2cfa857e63be1b418c91ad5ea1f8d136fd1b80fc856e1d4277274c3dea28011c\n2d53d7ec0c579fffd6710c956288537d46c719a93c6a04ac0d6550f75a6a6493\n2dd9d8c797fc695665326fc8fd0eb5cd292139fa478ccb5acb7fb352f7030063\n2dec81a678ddcac2b110acffe82427d857695180bd841e3f9736a554acf832af\n2e172afb1f43b359f1f0208da9386aefe97c0c1afe202abfe6ec09cdca820990\n2f929b067a59f88530b6bfa6f6889bc3a38adf88d594895973d1c8b2549fd93d\n30311520606ec99b6a810ae1a9a753df991777d374212423bb075c408a98ed74\n305a8baaf726d7c9e695bff31d3a6a61445999a4732f0a3e6174dc9dcbe43931\n308084bdd358e0bd3dc7f2b409d6f34cc119bce30216f44667fc2be43ff31722\n309ba76b12ecb5ce28b99f3445b2b5dc54c0564c3c0e24c17e4c89a94a5d0535\n30f65741053db713b3f328d31d3234b6fedbe31df65c1a8ea29be28146cab789\n317832f90f02c5e916b2ac0f3bcb8da9928d8e400b747b2c68e544e56adacf6b\n319b6cb8b0d24b38db5e3c6fbb13b062e2766d9af5ff9bccb8f439ac0d870e52\n33618678c167c5e07be02c49d0c43bcd90493ba5d83110a631409a4d3ccc1e51\n33d0a9b24c25852ce35274b4b1777484ccd21f44dbe35491cc926e5948c1ce3e\n3441821ebea04face181c9e2f4d0d09727c764827ac51b9e7fbadbebabeab225\n3477024fd843e46097840360f9cdee24b76bf5c593ed27a9aee7a5728a06aa51\n34c9f4eb2af8b8f46b1d88b74bde16f4614cd08948c2f1d817eb629afc512e7a\n351771edfc5db5665ded8aa4940257276b6526663c76e3b60b92a52584d8943c\n356d9903d16074f152fe8f2f0ef555d9959c53264228eae7373cad5cf35d4e85\n3582166ee20755856adf4882a8bfacb616fce4247911605a109c4862de421bcd\n358e47eaa1e9222252793fe0fb8c77028d4e0d4360b95a07c9fe6df6a2066556\n35ca5f142a7d7a3e4b59f1a767a31f87cb00d66348226bc64094ee3d1e46531c\n371a67232f7c871ec11332292c83cd9bb16063b91d58e86f0b76ef8817bc9465\n37ed50eea5a1e0bade3e6753793b6caeb061cd4c2f365658c257f69cab1f6288\n3852c7e45bd885b9537e276861ab50b99bb42f0f8e717d2f88174c62862ca3ff\n3874755f6222e83006fdad4d664ec0d9697c13af4fbe24b2f9a059bb13075186\n3a22fe593d9606d4f137461dd6802fd3918f9fbf36f4a65292be69670365e2ca\n3a3fee427e6ef7dfd0d82681e2bcee2d054f80287aea7dfa3fa4447666f929b9\n3a508d2dc03db46e7f97a2a30eabb62ab2886f3cedfea303de8f6a42e50d20eb\n3ab9cab6212fabd723a2c5a1949c2ded19980398b56e6080978e796f45cbbc90\n3b3f516ebc9a16cff287a5ffd3a1861a345a6d38bedbba74f1c0b0e0eac62afd\n3b75fc03a1d12b29bd2870eb1f6fdb44174dbd1118dfc11c31f127bd87bd27ef\n3b957237bc1e09740b58a414282393d3a91dde996b061e7061f4198fb03dab2e\n3bf7873f11823f4b64422f49c8248dd95c0d01f9ae9075ae3d233bbb21a3d875\n3bfd6bb152310f93daa6f4e1867c10572946e874b3a30c9ba8e0fcdeb590300b\n3ca8181367fc1258a418f7bf5044533c83e02a59c1a96def043295c429c297a8\n3d0ca3498d97edebd28dbc7035eced40baa4af199af09cbb7251792accaa69fe\n3ebd2ab34ba86e515feb79ffdeb7fc303a074a98ba39949b905dbde3ff4b7ec0\n3f9fc8e63f87e8a56d3eaef7db26f1b6db874d19f12abd5a752821b78d47661e\n40b00d701695d8ea5d59f95ac39e18004040c96d17fbc1a539317c674eca084b\n40bcdad218ac5f0885fc247d88fcad9f729f55c81c79d241a8f1559b6d8c0574\n4185b9369fc8bdcc7e7c68f2129b9a7442237cd0f836a4b6d13ef64bf0ef572a\n431b9b0c520a28375b5a0c18d0a5039dd62cbca7c4a0bcc25af3b763d4a81bec\n4327d27591871e9c8d317071a390d1b3dcedad05a9746175b005c41ea0d797b2\n43cf6b2ec0b0745ac2b87b4d8780f62e9050d3f5d50a1fcefa42d166191e84c6\n449a9c32e53a37c8a86e01c199155c8da3958b631088e10f6fe43c2119defe51\n449f41710769584b5e4eca8ecb4c76d5272605f27da2949e6285de0860d2cbc0\n449fe932622db3b49366a260ddd20077219f96fb2dc0f912ad4f60b087876f3b\n44ab6a09eedee848b072ea3acd0f4e781f9c43b8d4e3d62598e1024584bf0b01\n4596961c789d3b41916492918797724fe75128239fefc516c3ee75322b7926f0\n45c3bdef1819ba7029990e159f61543ed25781d13fb4dc5d4de52e803debd7d3\n45cc00f2ef95da6698bf590663e319d7c0ed4fb99d42dd3cf4060887da74fb81\n4829177d0b36abdd92c4ef0c7834cbc49f95232076bdd7e828f1f7cbb5ed80ec\n483b89aa683542f1c63e62f5f71ae8ae1f959caf1c379cd61230a71cd1036732\n4948e3dfe0483c2198b9616612071ead5474dffd223071cf0a567aea9aca0b9e\n49edc2f7715100fb0390916e52b3fd11a921f02e59509dc987f67840a36250fc\n4a424e0cb845cf6fd4d9fe62875552c7b89a4e0276cf16ebf46babe4656a794e\n4b274461c6d001a7a9aeaf5952b40ac4934d1be96b9c176edfd628a8f77e6df2\n4bf6a5ec42032bb8dbbb10d25fdc5211b2fe1ce44b6e577ef89dbda17697d819\n4c032609d377bd980e01f888e0b298600bf8af0e33c4271a1f3aaf76964dce06\n4c465a54e329ec7b0f4bc5f6acdfd3192707d6c0fbdf557339485581c5a6b3c1\n4d09672bcf5a2661eea00891bbb8191225a06619a849aece37ad10d9dedbde3e\n4d2cff9a0c8df3a7ef6100fda6f66e865a7670af6a18564767d8019b9ed2fd7b\n4d40de30a3db3bc4f241cb7f48e8497c11e8f20a99bf55788bdce17242029745\n4d4ebfcae4374165ea6ae7c7e18fd0ba5014c3c860ee2489c59e25ddd45e7a32\n4dbbb275960ab9e4ec2c66c8d3000f7c70c8dce5112df591b95db84e25efa6e9\n4e1c889de3764694d0dea41e5682fedb265eaf2cdbe72ff6c1f518747d709464\n4ee5850b63549794eb3ecd3d5f5673164ac16936e36ecc3700da886e3b616149\n4ff152d76db095f75c664dd48e41e8c9953fd0e784535883916383165e28a08e\n50a7ea80dd73232a17f98b5c83f62ec89989e892fe25b79b36f99b3872a7d182\n514ccfc78cb55988a238d3ac9dc83460aa88382c95d56bcc0559962d9fe481ef\n516a0e20327d6dfcedcf57e3056115e4fb29cdf4cb349003bdfc75c9b7f5c2cf\n52a4ac5a875be7a6c886035d54fb63f5f397dc43508c4831898f6b2f8debc7f3\n538b7673d507014d83af238876e03617396b70fe27f525f8205a4a96900fbb8e\n53ad09e4348767bece0165884bf40c10b72ae18444e3f414a850442f02385efc\n5419302571113e9aa74c7c0a9575333ca539b871a16c86ee92b35170b4ddc52e\n547ef286ee5f4e5dce533e982e6992ada67b7d727fdd3cfa6576f24c631a7ae6\n5488e8df5440ee5161fdfae3aeccd2ee396636430065c90e3f1f73870a975991\n54cb3328e778d87f76062b0550e3bc190f46384acd8efbe58c297265d1906e84\n54fe2d3416951cbc48f8718624c86a7ae58b6022a7fa75591b13f625cf53658b\n55ff2b0ec48b76e10c7ee18add5794005cd551697f96af865c763d50da78dd9c\n564fa390d9a9c26f986bf860d9091cbd84244bc1c8e3c9369f2f2e5b5fd99b92\n56d0da5b663ddd49955478c00ca03118c367ff7dd6a646b8c875b0acb207d1c5\n573a657d5e5fcb9574a758b0ab34b09c79d7ba374ceb71227c3dc88f009a3f6b\n573e1480b500c395f8d3f1800e1998bf553af0d3d43039333d33cf37d08f64e5\n57b49733c5a3c268b013553635a826e6a1b10e699bbd19c3b842375fe0adf344\n57d88f45e479ce3821839b2706d667758c63ac769d76800d815c73d2507c1e42\n58406ed8ef944831c413c3424dc2b07e59aef13eb1ff16acbb3402b38b5de0bd\n58c593bcb98386e7fd42a1d34e291db93477624b164e83ab2afa3caa90d1d921\n58cc121d37fb7f1b4a5252024d88415936781e540252b8f734faeedd29b682d5\n5908488d940e846cc121c768758da9b1bd5b9922417e20c9101a4e254fa98af8\n5953af5080d981b554529971903d8bee9871457a4361b51f04ba04f43793dd8f\n5afb7932e9c7328f4fb1d7a8166a3699d6cdc5192b93758a75e9956f1513c5a3\n5afcbfd0dd64392aa1e233b996d0bfb4354ee7119f30ae111c33d0fe4df11590\n5b0bde771bc67c505d1b59405cbcad0a2766ec3ee4e35852e959552c1b454233\n5b12df18e4ae4df5af06052584cf0e6bef58ee2a220653890636eef88a944e14\n5b2ccfb94dedf2ec8797c0404fc324888e35ab903c41bb26f070552033ca8e6c\n5ba4facefc949c920d7054813a3e846b000969da2ed860148bdfd18456f59bcc\n5bb8508ff8ec8683fc6a8aa6bd470f6feb3af4eccdca07f51a1ebc9dad67cfb8\n5bda829acd824821bc1f3f6573cf065d364653d5322f033a4af943f7a6170566\n5c235b945b25b9905b9b0429ce59f1db51d0d0c7d48c2c21ab9f3ca54b0715e6\n5cc036b65f7f2d5480e2be111a561f3713ac021683a9a9138dc49492a29ce856\n5d21acedb3015c1208b31778561f8b1079cca7487399300390c3947f691e3974\n5d2c98fd6fda3c7d739461c3b3d4a0c7f8456121a14519dc5955a1775227b053\n5ddbfba2519484316e4b7ccabfa605e6e6fd96c3d87ac8cdfd2c134571a15311\n5f9d29d6388c700f35a3c29fa1b1ce0c1cba6667d05fdb70bd1e89004dcf71ed\n602f267432e7a573e1092f1cf48135c82d0fbc8722bc028b9330ec801a40bb18\n6034456567632f4b48dc3dfbb98534b5953c151990f4235df6c912c0a9c08397\n608ff81c8a0c8b622f6648a9c7f935d85f0c503f515ef2ac3728387be1953ee8\n60cb718759bff13f81c4055a7679e81326f78b6a193a2d856546097c949b20ff\n615985773f1469fbc00915b3e82d1d4942051c09ddea2667e37ad361ed2e9d59\n619429303c1af7540916509fe7900cf483eba4391b06aac87ff7f66ca1ab6483\n61a15ccbfebb9d2fc54c068472a75d7babfb3f48fea008470e7db807585f9510\n61dc249314d7b965eb4561ec739eab9b0f60af55c97b25ced8cb2a42a0be128e\n62057502c387145ed4f8f7f0d5e5bedcb72d3bcec15fa71cb0310dee32871461\n623cf6987b3fac8f384c09f40d98c5e739c097aa9a9627054542aa27f7d38db1\n62570c4ff1c5ab6d9d383aba9f25e604768520b4266afd40fdf4734a694c8bc3\n63d981a107091e1e3059102ce08870744dde173afe324bc2274c17d42f661778\n648636ee314d7bdba3ab2fc0fe49a863de35c3e2caf619039f678df67b526868\n64eeef16fdc4e26523d27bfa71a1d38d2cb2e4fa116c0d0ea56b1322f806f0b9\n66236902b874b7e4b3891db63a69f6d56f6edcec6aca7ba3c6871d73e7b4c34f\n66612c188d73e931e1863af2c99d2af782c32f65fd97d224abb40bbadb87263f\n670ebd9d674be236b9bf0b28650ad3f68e1891b06e16a9021fd069ca7ef32b78\n68f833de9f8c631cedd7031b8ed9b908c42cbbc1e14254722728a8b7d596fd4c\n693bc64581275f04fc456da74f031d583733360a1f6032fa38b3fbf592ff4352\n6aa7dd0c88bec4f96cdd497f9c37779733033d9ec6513307461302d36bd32ac7\n6af82abb29539000be4696884fc822d3cafcb2105906dc7582c92dccad8948c5\n6b61ab2e3ff0e2c7a55fd71e290b51e142555cf82bc7574fc27326735e8acbd1\n6b6d4e6ff52de473a4b6f8bd0f11ae22242d508cc4117ff38ec39cbb88088aaa\n6b72b61b80060a9e79a4747f9c5d5af135af9db466681c2d1086f784c7130699\n6bc8cda54f5b66a2a27d962ac219f8075bf7cc43b87ba0c9e776404370429e80\n6bd18a218d25247dc456aed124c066a6397fb93086e860e4d04014bfa9c9555d\n6bd330234b763b77796d4804de8e224881c0fc8dd02650fa708b2edfd8c7461f\n6c85029f850d392791e13f74963391054ff54e508967bbd091ee510e9e58e011\n6eefe1f0d9c2d2c2380db3ecd2113a566ace7dfc917687bb5033b4af5b8293aa\n6f8197baf738986a1ec3b6ba92b567863d897a739376b7cec5599ad6cecafdfc\n6fb82031f7fc5f4fa6e0bc2ef3421db19036b5c2cdd2725009ab465d66d61d72\n6fc83b33896f58a4a067d8fdcf51f15d4ae9be05d8c3815d23336f1f2a8c45a1\n6fe2df6de1d962b90146c822bcefc84d0d3d6926fdfbacd3acdc9de830ee5622\n700afb1cd830a808e3c6125749612e5d23fd9f9726049a9e0c2061997514e1a7\n70827e40a7155391984e56703c6df3392fb4a94bbd6c7008da6a6ca3244965d9\n724b6b7044522f6d5ea35b55f8fa71d0a45a28687be2b7cac3149943ab816eec\n72b18a405555ad491721e29454e5cd325055ce81a9e78524b56f2c058a4d2327\n72e8c49dea44787114fd191f9e97e260f961c6e7ae4715bc95cc91db8d91a4e3\n75120baa6abcbfe750a4eb223b8c10ae6bc3bebdda7b00d9a78bc2472fa28625\n751f421d322940d6efe3bd570a66ecda16d08a1b90bc32a6d7ae1af89856fd49\n76c44d1addac92a65f1331f2d93f4e3b130bd4e538a6e5239c3ac1f4c403608a\n76c4f14e35210f87a29e93c46dbb25c8f5dc5c04d1d3134672708bcdfbc7e959\n76faaed50ed6ea6814ac36199964b86fb09ba7f41a6f213bceaa80d625adc2e1\n777f7c4269279951ae05b56e806745e613297d411d048c0bce8964afd7d71a4b\n77ceeb87f560775ac150b8b9b09684ed3e806d0af6f26cce8f10c5fc280f5df2\n785555c0cbb49dad835635217085287a8cc61c27d26f0e106b70c1dfd05784dc\n7978812d0e2e034ee1f9c141f019705582fcaa290e4a01c6c75a62753285cb23\n797945873ca2a95f028671714b71eb3f883efe9dae7fcd3fc0ea1521efb73aaa\n79dfcbc9361edd3a1ffe81a5bdaa22a197ad1341f3fa64b86a646c2607d6b324\n7aa1aaa5e032a980f434c8ed63efb57ab0d338d6154c47f7bb75afdc89f43c04\n7aae06bc4558829473071defec0b7ab3bfa9c5005548a13da95596bb6a66d105\n7ac468eb217b7058d22c1711285d21949b4121bf3fa3217e3e51453666ebecff\n7af09f98ec299ba0658d759eebc4c34e1c98289ea6ce37f233e9f5e4e2fc84f4\n7b38c9173ebe69b4c6ba7e703c0c27f39305d9b2910f46405993d2ea7a963b80\n7b5987a24dd57325e82812371b3f4df7edc528e0526754ba94cf3a1ea4df25d2\n7ba20aa731cc21af74a8d940254176cbad1bdc44f240b550341c6d9c27509daa\n7c0157913223365720209ac83ff2e0b1b2b460173acd615c67646014093a2b97\n7c318172e976ae5a962c9c7a4e9fe46d7fb985765ddd3a3e2108e893a90b92b2\n7d40ea6ead1bec903f26d9046d291aedcb12a584b4d3b337ea252b34c7d86072\n7f2b154541166210f468d89bb0a7184f10e51168a181dbb8b686c14654ffa317\n7f4d7b549d0f1a110191e2aded872943d85892bc30667f19fe9de97a5370b08e\n8055957570d7b38f0acecdb56f3078a963a1a7307ca03fcca62212e0e95e5845\n80632d6be60c8462e50d51bcf5caf15308931603095d6b5e772a115cd0d0470c\n813f41ef376c3cbcc9d6e2ce6a51c2ee068226d1c1b13404eb238dcfdd447c97\n8175a55b711c948fe383bd3b91b6ca1b9e048a5241e0be13aff31ce2674fbe6d\n81e2dd950e6df28a4fe202a40afa98b202981f65a5ca05b389749290eb87c883\n831218e6a1a54b23d4be56c5799854e7eb978811b89215319dc138900bd563e6\n84e642d75ae6ece8147272418b6fe13d04db8d076fe306c4acedc329fceab564\n84eeec681987753029eb83ea5f3ff7e8b5697783cdb2035f2882d40c9a3f1029\n866a8cba7bfe1ea73e383d6cf492e53752579140c8b833bb56839a55bf79d855\n86f9087eb1d0875ffb1a28cca7645b14d6c66f995c7d96aa13969d2f8115d533\n876423522bdec1602917b94163a21e05fc7b692045219b7bc96cdaf638c33c25\n88678981648b184b23b6c04999f29210cbe351f85b61d2bf99e306fd67a2998a\n88d5a03f8ecd459f076a06e0d5035149193bfdd727c30905de19054dcb9018ae\n89be66f88612aae541f5843abcd9c015832b5d6c54a28103b3019f7f38df8a6d\n8a26b134fe9343c0c794513dae7787b7ac1debec3bb2a7096ab0b874a31d8175\n8a65e41c630d85c0004ce1772ff66fbc87aca34cb165f695255b39343fcfc832\n8aa1a883f61f0bb5af3d3d60acaaf33af45ef4fbffaac15ae838bc1ce37b6fbf\n8b12e18670e4b24d03567d1e17c0c24fadf0ea2c1e763983dd6bb4c44b7376a6\n8b77284d6f37ab3fc826139ebadaec3b9d81c552fe525c3547bbbd6c65ac0d83\n8bef203fce625e4d8c89dca728158be4662dfdfdcd4dc73a6aa39a908c1631bc\n8c3ef7aa7ed29b62a65b1c394d2b4a24aa3da25aebfdf3d29dbfc8ad1b08e95a\n8cdbdda8b3a64c97409c0160bcfb06eb8e876cedc3691aa63ca16dbafae6f948\n8d29c5a03e0560c8f9338e8eb7bccf47930149c8173f9ba4b9279fb87d86cf6d\n8d9b4205ddb10fa49a2973b4f3a2dc6923407ae015081e1a52c4b4c2fe8faa53\n8de0b1a2e8f614af29fe5fafeaa5bdf55e6b3e65edf36355f19b707f7649ce2b\n8e8a7a14749d0b2e48de3d10e2e80063f17b165ad921c8afc0623f08500f3259\n8ecdb93582b2d5270457b36651b62776256ade3aaa2d7432ae65c14f07432d49\n8efed2e62c919e6d70a2ab548b1a33014877fe8a23f177ef25a9dee25ffe8842\n8f6597cd978c060378177df76e554d0578b97eab471e237dbe0adc0dd0d93d63\n8f6e49e474ebb649a1e99662243d51a46cc9ba0c9c8f1efe2e2b662a81b48de1\n8fdc34509a0c3721f7b5e235c8a93e1f553343aa17ad103a1e89e3509a3e1570\n91cc2e0d4d6e2c1ad59a8d63bcbe3e2ea8bc7f8e642e942a0113450181e73379\n92e7e86e765e05ce331c07a6d14f0a696eac7ee40058699243900f40b696d7aa\n930f246a8e4ff273a72a6e4b3cf8e8caff94fca4eaf1dbe6f93ba37b8195c0a0\n93c5638e7e6433b5c9cc87c152bcbe28873d2f9d6a392cca0642520807542a77\n93cfd412c7de5210bbd262ec3a602cfea65072e9272e9fce9b5339a5b9436eb7\n942d56861fc83e195e9c559a000bb86627d8682f8dcc2300818458e5b6850dd0\n94a5a37c3b1153d5c5aef2eca53c960b9f21f2ef1758209d7ec502ec324b03a3\n9520aff4efe87bd8f3901652fa2dde9b4bc9c679325966145ce00c1ca33f35de\n958114e5f37d5e1420b410bd716753b3e874b175f2b6958ebf1ec2bdf776e41f\n9586e48a9a4353f11898a6a4b7475a91574e8af82e99c4b7a5e1f1b18f345f7a\n9620c33d8ef2772dbc5bd152429f507bd7fafb27e12109003292b671e556b089\n97126a9791f0c1176e4563ad679a301dac27c59011f579e808bbd6e9f4cd1034\n97158b2fe38783d88d4e44ba1b7bc6c84f225f8b35fcccc2f9265c65f14e7c8b\n9774c82396327929fea05e40ae153cabf0107178b2ae3e40a5709b409793887e\n98a463483fe3a56deacc8bc00ab8aa62668bd40ad0c70bbe7deb10d3e4aeb0c0\n98c5ead89cd066637efd5c93a6edc55c85908eb66807471f0d246d5457341f9c\n9a71a416f98971aa14f63ef91242654cc9191a1414ce8bbd38066fe94559aa4f\n9b25b8ffd5f52b6c3d235a42d51d380503d1f80b61ef0f62eeb696f5977c38e6\n9bb6e39d5f4415bc7554842ee5d1280403a602f2ba56122b87f453a62d37c06e\n9bf9f3dcadeb152a9a26b23e2fb199dcf9b2b17660b1646326f5b10cbf51eaee\n9c95eae11da041189e84cda20bdfb75716a6594684de4b6ce12a9aaadbb874c9\n9cbc0700317361236a9fca2eb1f8f79e3a7da17b1970c179cf453921a6136001\n9cdac2870cfe65b6cb61bd151020068a3b427118a27343767b07ea39483fee32\n9d429167633b4d9d7f41544a461975cf8e688a3affa6a8916799202874809f2a\n9e4f8ec60a0d622a02c0e16eedcc0101f88ddefbcec2383946c4572b57a1e43a\n9ebcfaf2322932d464f15b5662cae4d669b2d785b8299556d73fffcae8365d32\n9f073db4acd7e634fd578af50d4e77218742f63a4d423a99808d6fd7cb0d3cdb\n9facc652efe19f634639585d692a53dd6c2a8e2f0c9baebdfd85b9b41ec58851\na022908f1b7880838dbc0411e50828e64b4f5e0263afdf04295e30bb2ff58005\na02ec007ae8feddb758078b1dfb8010c26886fd3c8babdc308ead8b4a63acbdb\na0afead3b4fe393f6a6159de040ecb2e66f8a89090abf0d0bf5b8e1d38ae667c\na0de55384fada5cbc46bd7a41f6feeef93b67d088497c7316079ccec39c2a834\na101a00fea63f0c43abe5323f4f890bec881eb0caa3bc8498991ff5fd207ed91\na102535b0e88374bea4a1cfd9ee7cb3822ff54f4ab2a9845d428ec22f9ee2288\na1777737270c5f96c4523dff76e4097756f8f7d4c9d59bac079e31f9510deabd\na1f50f101bc471e2d6967ebdb8ba81150588609e769f3b960f0801e4da5fdc6f\na22b7882fa85b9f0fcef659a7b82bfcddf01710f9a7617a9e036e84ac6901841\na246bcaa64af48ee5ca181cd594c0fc43466e7614406eb8bc01199a16ebc95d0\na3a1b8f9794ef589b71faa9f35fd97ad6761c4488718fbcf766e95e31afa8606\na3a65623e079af7988b0c1cf1e54041003c6d730c91ecf200b71c47b93a67ed6\na486f6ed4b8781e7883e433d06a83dd66db3e8b36d45b9976c4214820ee22629\na4c729efb5059893a8b62c7abeba171cb516836f8a20468f6b176dfe2f6f84d1\na5e695fce80dc03efb6665a9ec14500ab47f4ee9f6437531388dd3cc32c90db1\na6001531274f9ad16e0ced40380f9667b9149558dea7053f7a7db18f5cd028c0\na6515d73077866808ad4cb837ecdac33612527b8a1041e82135e40fce2bb9380\na6593632dcbbe4c9e9429a9cec573d26fd8c91a47d554d315f25e7c2e0280ee3\na65bbfc5673e8053b6ce49f39c79cf3a846fe5cc46dd93105f74fb07cf44606d\na6e81120d1cb9f71f8a25f90a5d56c4b714a642fc496a705e38921fd90a3f69c\na7a581e6760df4701941670e73d72533e3b0fbd7563488ad92772b41f7709710\na815a986800a95de0957116c6585deea8ffb6ee09ad00ccc687306937ac698d0\na891bbc89143bca7a717386144eb061ec2d599cba24681389bcb3a2fedb8ff8c\na90cad45551d62c5cfa89517df8eb5e8f2f87f1a6e6678e606907afcbad91731\na9d884ba0929dac87c2052ce5b15034163685317d7cff45c40b0f7bd9bd4d9e7\naa47f0b303b1d525b52452ae3a8553b2d61d719a28aee547e2ef1fc6730a078f\naa4d989d262c618ac2793579e200cc71b3767f84698ae5f669867f23cdfe2568\naa83f5b4fca02ae43a6b9456ab42707b0beabc6e7c5c4e66c0d2572fb80f3615\naaa52100fafaa50877e777229cdf6cde7c422f145ff6719449b80631d9a3b0f6\nabbfff07379bceb69dba41dad8b0db5eb80cc8baf3d4af87b7ee20b0dac32215\nabc781c8721fe04b877a7e324494eb75aa5bce94950a0e4e4d7d6ffc9e74da62\nac782d2cad7f515ce7276926209820e386248e3d619b2df81e22d5e3c160b7cb\nac8169a0debed11560f3f0e246c05ea82d03c66346f1576cc8268554cb3f549f\nad9d305cbf193d4250743ead466bdaefe910835d7e352c544e22320e8336f5c1\nadc315bd40d699fd4e4effbcce81cd7162851007f485d754ad3b0472f73a86df\nae9f76b5360df3f60f3cdd389652b96e823080bb830dd8c79e7f1e597d51bc1c\naf8621ef0db8c26b0bce6385bd5609b584bfd678fcf7a234b8a15e6bb05c15ac\nafa272dc01825d4b929b3bfc79a10f68dd3c163c450d858b964d1ce0bc93e131\nb0d6dfcc95e4d087d232378f860fc3ef9f95ea5a4c26d623a0be091f820a793f\nb0defa611b75645c0283464ee4163917bad382d335b61e8509f065bf371fa15f\nb1a239838c7dbb34ffea851ad537899f24da62f4e3f3fd6d835ff7b922f27313\nb1e3aeb0c56261c17eb71c747d116057b8da7e8c8a6845bdc01b2b3ee2299229\nb1eb0123fe2d8c825694b193efb7b923d95effac9558ee4eaf3116374c2c94fe\nb1f23c4d27afed8af7b6b64793a3760bfea31b65f582d48aaa62d2b988ef2eac\nb24ea5c268469a95ed155eeaf809e36030b78a2eb530a0cb2380cdc1ccdb7dd1\nb2c23ddb04531158da6a0abcaca78fec0ae5c6f64f60166e4f36f4a161efd76f\nb2c5d8653c621207e97b699e5c4c05d13df4f02d9db3e594b1f0c22e5b746aae\nb3a9f4c9035a0df7e033b18c63bfb0f0d87ff5a4d9aa8bdf417159bb733abb80\nb3b1626f8ad156acb2963d1faa6a368f9378a266c3b90d9321087fdc5b3032b4\nb3bfd873fca7ff9b2e90f507dfdbe165bb8c153399b6ba5829aa59bae677a91d\nb4d902d42c93dea77b541456f8d905f35eeb24fc3a5b0b15b5678d78e0aabe0c\nb4de1e3eec159d8af1bd5447696f8996c31709edaf33e26ba9613816705847db\nb560dba92fbf2af785739efced50d5866c86dc4dada9be3832138bef4c3524d2\nb61d3fb0d0ebbee018346e0adeff9e9178f33aa95262779b3c196f93b4ace895\nb67a6e5da8b1cfa5319d94a7d3f8b706725753346c37a4636bf7382e98b3c5df\nb6c9b58de0388891221b8f7a83cbf0b8f8379b51b5c9a127bf43a4fc49f1cc48\nb6d50fa22380ae3a7e8c52c5bc44a254e7b2596fd8927980dbe2c160cb5689b5\nb6edad733399c83c8eb7a59c0d37b54e10cc0d59894e39ff843884d84f61dee1\nb76ff33ae9da28f9cd8bdce465d45f1eca399db3ffa83847535708e0d511fe38\nb7a86f4968071e0f963fa87ef314fdd1b6c73a66355431cc53a37e193ba6be9b\nb82548ab19466b461614e6055aaf49fbc24c03a2d20e65575b680c7c28268807\nb8fdc02d915206bb2564e1f7da962f2b9d9d491b11afa00a76622b7932366480\nb98681c74842c4058bd2f88b06063731c26a90da083b1ef348e0ec734c58752b\nba3997edd3fcb2f823ecdf870d2b607f08bff848f72a5cf72340bae5aca7c5ce\nbb481eae02085bbae08742f702b9ab7d8b2ff9df2fbefeee9fac51f7c77dd01f\nbb8ebf465c968a5f6f715de5d9e2e664afd1bcaa533e0e3352ecea1cc5b6fb0d\nbbce7ebc40323a0eff6574d0c3842f50f907f55fbfb46c777f0ed9a49e98ff9b\nbbfc4aab5645637680fa0ef00925eea733b93099f1944c0aea09b78af1d4eef2\nbc115ff727e997a88f7cfe4ce817745731a6c753cb9fab6a36e7e66b415a1d3d\nbde3727f3a9e8b2b58f383ebc762b2157eb50cdbff23e69b025418b43967556b\nbe1916d0e5592c17f971315b5de720ef6894173087399daed94a52ef109c1572\nbe26966900aa0e5b41d5a8ecafe04281b37deb05c5cd027968d7b74143398174\nbf566e75d5cb0196de4139573f8bbbda0fa38d5048edf7267fe8793dcc094a66\nbf7691b0a79811fa068b7408cbce636a73f01ef9e971a95da1a2d96df73782b6\nbfe8ef193a68a0a86a5e4ae1ddc27bda3f9ffe170494395be4030ba72737c565\nbff985591dd5d6303018a6e9a3dcfb336771a414ad4605c24ce1c1155fc86a96\nc00ae67f72816daee468474026e30705003b2d3501f123579a4f0a6366b66aa1\nc043d5ac9dd466052e53491d0d513b0684f493d320b820f6dc2e05330ce58ec3\nc04fa1a74a980d790ba6f3e595fd9851f14370bb71c7cbb7846c33ca9d72687f\nc0c4a829c8d33d16a02f5dc0411597329f4b4d726ed6a22b5530cf6c8e106c4e\nc15c652c08153fb781a5349123ab8f80bb2a8680a41eb8e89e547ae01b7a5441\nc169a7782a69ea2f38f64d2739de189e88adbcfd4a829721def8c89ecabe8b71\nc2a646a819f59a4e816e0ee8ea00ba10d5de9ac20b5a435c41192637790dabee\nc304a1fdf3bca2f4b4580d2cac59942e2224a7678001bf5ed9d9852f57708932\nc322c72b9d411e631580fee9312885088b4bb14ed297aa4b246ec943533b3ffb\nc35e6e3ea39a718e1b7aff66e4cc678efd662f9b5336b74d69c1d6bca7aaf288\nc3bec1066aae20f48b82975e7e8b684cd67635a8baf211e4d9e3e13bc54c5d06\nc44ed955eb2e5c8d820b01477e122b32eff6dd475343e11229c33d8af3473b22\nc53326fe49fc26b7fe602b9d8c0c2da2cb157690b44c2b9351a93f8d9bd8043d\nc620631271a56407d6d69fa1a69451ca99c50dcc30e29db04cf6fb7cacbde8cb\nc6216cdc42f61bc345434986db42e2ef9b9741aee3210b7a808e952e319d2305\nc6de542205b891eed5c40e6d8ae3d03a6ca39b26dc445b4dbc64340d4d64dd2d\nc75139ef0546d2240b37afb3219eb74a06b7977818697d5c3138796472483af3\nc7d546766518703580f63d5d8f11d54971044753f53c0b257d19c2f99d4bfdd0\nc89ac06daef5c819309f03d6a35792d1a8a66abb8cb3414013ffe71d3dd9fe96\nc8ca945abc29d262a5525e4c2585541bba33fa77c86a47c94575d8e5b54c83fb\nc901794d1a421d52e5734500c0a2a8ca84651fb93b19cec2f411855e70cae339\nc96109cbebcf206f20035cbde414e43872074eee8d839ba214feed9cd36277a1\nc9f305be17312bdb9530fb4f1adc6d29730ddbe0e74730cbf031de174bf437b7\ncab4875269f44a701c5e58190a1d2f6fcb577ea79d842522dcab20ccb39b7ad2\ncb4df20a83b2f38b394c67f1d9d4aef29f9794d5345da3576318374ec3a11490\ncc88627344305b9a9b07f8bd042cb074c7a834c13de67ff4b24914ac68f07f6e\ncdab367b30db47061df837c1ae9fa875d6057614f797332d37d3513517d6c694\nce37f6dd0615d45e66e41a8f2ed6fbc0bbe3103a290394ad474207507710eacc\nce88df7356d9d4a8d5944a93768f4c4b593de2d35616f7448c2b37b9fd60dd1f\nce9e1a58b58940039ae841466198b72ea21cc90584039a9294b47f5aef17ddfa\ncf26c41245febfe67c2a1682cc4ee8752ee40ae3e49610314f45923b8bf5b08a\ncfabf7379c5591d40aa4a20c86b4197c6a25ab55887a9fca4f06c2dfc0f0e973\nd0f2a00d3155c243048bc48944aef93fb08e2258d1fa5f9ccadd9140082bc22f\nd1b173875e2261f55014bd27bd7174b9ae1c769338c1b31b5d737e9e60175993\nd1ba6089cae2f90cb7275ece10ca393c25f60ea17e5c9c3cea2399d31fd41869\nd1dbc6ee7c44a7027e935d040e496793186b884a1028d0e26284a206c6f5aff0\nd256b32adda37f2301c9e46f34b7f9a36cce273256369ceb5dc2c73c3007e3c4\nd2815f2f616d92be35c7e8dcfe592deec88516aef9ffc9b21257f52b7d6d0354\nd2ce593bddf9998ce3b76328c0151d0ba4b644c293aca7f6254e521c448b305f\nd32ea6d318626ca14a967d0c1ad3218aebfe636624a8d1173f5150dde8ff38cf\nd35f25c8e3f7fca5232fc4d5e3faf14b025b20b3731af77fe971a5e2e9d69d28\nd3ce382f190ee24729bd2e80684c11bef72bc9c733cdbbc19a17d2c1b2e775f7\nd4d6c683f249d82518431603bf0206d05f2114ac871a99ffade0f5f61cf167e1\nd4d88391bc399a3715440d4da9f8b7a973e010dc1edd9551df2e5a538685add5\nd52958107d0b1f0288f50f346a833df3df485b92d5516cfcb536e73ab7adafd0\nd6a880b1f6056f3086679de5c810e7af87cdf3bbbd0533a83e3681817fce40fc\nd751ccb64fa767a65a966061218438bd1860695d96bbef11fdb2f0d3b8dedba8\nd7d12a2acc47a94961aeb56fd56e8a0873016af75f5dd10915de9db8af8e4f5e\nd7db360fabfce9828559a21f6bffff589ae868e0dc6101d7c1212de34a25e3cb\nd7ec8003735996458b56ccb8ae34d080eb2a6adabef931323239632515b4b220\nd7fc0d0a7339211f2433829c6553b762e2b9ef82cfe218d58ecae6643fa8e9c7\nd827a7d80fc67487a3237135e0d43ae01b7bbcb135e1a167601fc974a8348c51\nd8607b21411c9c8ab532faaeba15f8818a92025897950f94ee4da4f74f53660a\nd910b2b1be8406caecfe31a503d412ffc4e3d488286242ebc7381836121dd4ef\nda31f2aa8601afec5c45180a2c448cb9c4a8ec7b35e75190d6ba3588f69058c8\nda5f98f2b8a64eee735a398de48ed42cd31bf17a6063db46a9e0783ac13cd844\nda79a2b105f055ce75404a04bf53bbb51d518d9381af7b4ac714c137f101d920\nda8ca06ccbb4e2a8718f7c2939ef6cc3a4088981f660842ad885a8273e740d55\ndabfee30b46d23569c63fa7253ef10b2407fbe8023035a5030252313cb718097\ndad607a203483439fcbc2acecd0a39fb5e5a94a32a94348f5c802c79cfeb6e7c\ndae976f161fe42dc58dee87d4bf2eb9f65736597cab0114138641b2a39a5c42b\ndb45946a4412a2137674ec075b6892ccd682b77826aba618210569bbc65cf2b0\ndbbfe08a52688d0ac8de9161cbb17cb201e3991aacab8ab8a77fe0e203a69481\ndbe5ad05b6f87018159a3228c1d1725892a1bfb9fa9f8fcc2e8bfe70d69d0355\ndd54adb80393de7769b9853c0aa2ee9b240905d0e99c59d4ccd99401f327aa05\nddf1bf458312de2895dd9cc5ce7ec9d334ad54c35edc96ad6001d20b1d8588d8\ndec1764c00e8b3c4bf1fc7a2fda341279218ff894186b0c2664128348683c757\ndf33b11184427e05c8a450f921586685975fe975f57315e686a0f26fddb93db1\ndf53d0b6c2c4e45d759b2c474011e2b2b32552cd100ca4b22388ab9ca1750ee2\ndf5cdd0ebe1bdf8dc870bc294b8f08961e083bc7f9be69e268454aa9091808b9\ndf9a4212ecb67bb4e58eba62f293b91f9d6f1dde73e38fa891c75661d419fc97\ne1bcb583985325d0ef5f3ef52957d0371c96d4af767b13e48102bca9d5351a9b\ne216ec5063d3562b793e434c491051bd8867f6c2e571e41137c7c560cc0e6a03\ne23e11414ee645b51081fb202d38b793f0c8ef2940f8228ded384899d21b02c2\ne2d22d3d283915df8350d039278e314a23e6e8f2b41bdfc16df849e22dd13b36\ne321cfa987e77c21373a0f8b1236c83d6636306949a82a7f5b07fc0838e7777f\ne4537e7893e631f3ba6ae5b1023e24b233c78249a31c2f5e561f6c4cad88fcf6\ne4ae1ceddb279bac30273ca7ac480025ce2e7287328f5272234b5bbca6d13135\ne4fc936ba57a936aaa5941ccc70946ab18fcebcb6e8d85a097c584aff9ca4d88\ne50ac10d1dce6496d092d966784ed3795969128ca0bc58199a36d558ed529203\ne52960d31f8bddf85400259beb4521383f5ceface1080be3429f2f926cc9b5c2\ne5384c905e9879cb6e8ff5250fb03155bc1db035d8dde458eece9078b7de8ff1\ne5a6c5e01e6a4ef676a2d975374e995dd55792ea317a8e110bebc37da83a4ce8\ne5a7b8a9924b26b3abf039255a8a3bb00258f4966f68ff3349560b4350af9367\ne5aeb5b3577abbebe8982b5dd7d22c4257250ad3000661a42f38bf9248d291fd\ne5f8ad0f0a43af8ca57e31e16800108abdfb44a7e962a71d246f72d2dbde42bf\ne66a97b2c77f3d66a7d3cebbc6a36c8c6259368a397f7b67647ed80ad53aa776\ne66f25e175abab08ecb4e5f6859db64a211e0ddffb262d7e727b9d9bd4aad2d2\ne7a3a7c99483c243742b6cfa74e81cd48f126dcef004016ad0151df6c16a6243\ne81c758e1ca177b0942ecad62cf8d321ffc315376135bcbed3df932a6e5b40c0\ne856511ac1c34d24320eb7c56c05a4a3340d06667b4f5b8e8df615d415c7f650\ne9b8ad127f2163438b6236c74938f43d7b4863aaf39a16367f4af59bfd96597b\nea00f5a91ca75e745d675201cc62d7db266f8e2787033e15a7dd5f1cc5c0ad72\nead9464a50a17f74bf1b6471d94ecce8d887cf518c8fedc6c6048eb948bc4e49\neb96fc6cbf6880bf05c4309857ae33844a4bc2152e228eff31024e5265cf9fc3\nec031f176dafe0b36547068ce42eab39428ec7995dac1b3ea52d1db79b61fdeb\nec486143ecfec847c22cd8cbc207d85312bcf38e61c9b9a805e0d12add62da8d\necb36c90cdd20245d89173c106f3c6a2d124d07bdea0ae202fb1efa49b0cd169\ned4b8e0d756836be7acb2e2b7799c473b52424e3092a71d3c6d23558e500dc4c\ned8c31b001a0c23c33402f94a5ee6b0209e0c6419eb52d5d02255513e3a672fc\nedd36ed822e7ed760ff73e0524df22aa5bf5c565efcdc6c39603239c0896e7a8\nee927e8255096971ddae1bd975cf80c4ad7c847c82d0b5f5dd2ddfe5407007ee\neeb142344e9de3250ab748f93940bf06be70d5078337680998468a134a101698\nef6634efb46567d87b811be786b18c4cd0e2cda23d79b65d6afe0d259ef3ade6\nf01a9742c43a69f087700a43893f713878e537bae8e44f76b957f09519601ad6\nf0a75e0322f11cead4219aa530673fe5eef67580fb6fccc254963c9fc6b58aa1\nf0c9f135c62572f3669a75b2c735e4477dc77fac85e653426ee2b3bcfbed7aaf\nf113626a04125d97b27f21b45a0ce9a686d73dee7b5dbc0725d49194ba0203bd\nf20eb4592e7d3cf58d421a9c34832d33adcdcbd0e17b7bf009a013847608da27\nf26f4c2c70c38fe12e00d5a814d5116691f2ca548908126923fd76ddd665ed24\nf29fd9c52e04403cd2c7d43b6fe2479292e53b2f61969d25256d2d2aca7c6a81\nf35ab34528e3e2d2589d24cbffc0e10024dfc474a68585d0b5feb7b05aa0067f\nf43169e3d8b4f71e687945b9e72cbfdfe2e40e68842568e6a30c60d64c1378b6\nf487cc82271cf84b4414552aa8b0a9d82d902451ebe8e8bc639d4121c1672ff7\nf4c4db3df4ff0de90f44b027fc2e28c16bf7e5c75ea75b0a9762bbb7ac86e7a3\nf4faa3a409014db1865074c5f66a0255f71ae3faba03265da0b3b91f68e8a8f0\nf534b43bf37ff946a310a0f08315d76c3fb3394681cf523acef7c0682240072a\nf67e72b7fe0b1e3648ea745ffd395c80705c89b0c0c48227991fe6f5815b2a18\nf6863b83d75e5927b30e2e326405b588293283c25aaef2251b30c343296b9cb1\nf6b16c885c0b2bc0d0eb2bb2eeb0a2753ebafb5a7a91da10e89b0b0478984637\nf6cb37ebf29c225284c8415962f7287abe7007fae8fe3d8a3899b608b832d7d5\nf728de04267283f0b4daab9a840e7433b2c6034baf195fd526850439c9297687\nf73e37957c74f554be132986f38b6f1d75339f636dfe2b681a0cf3f88d2733af\nf7eaaf420b5204c4a42577428b7cd897a53ef07b759ccbba3ed30a3548ca5605\nf81ca7ee25e733ff37240c34c8e3044d9937bb0166e315952ebde3f237ecb86f\nf8e74d4006dd68c1dbe68df7be905835e00d8ba4916f3b18884509a15fdc0b55\nf93ec5e683d81005ffc2a84a1c0299b2406ad14b764b824e013f7ca3a13833b5\nf9ac03b0344ce8c48bc058448541f9211a1e5f4c94fdaf633dd534328d8610ab\nf9ea1a1159c33f39bbe5f18bb278d961188b40508277eab7c0b4b91219b37b5d\nfa73f24532b3667718ede7ac5c2e24ad7d3cae17b0a42ed17bbb81b15c28f4ae\nfa751ff3a6332c95cb5cb1d28563553914295e9e7d35c4b6bd267241e8a0787c\nfadeb0ab092833f27daaeb3e24223eb090f9536b83f68cde8f49df7c544f711b\nfc22db33a2495f58f118bc182c0087e140df14ccb8dad51373e1a54381f683de\nfc345dac2205deb169bd70197f07f053bada80b61ffa69fdfb490758323ead69\nfc5452f612a0f972fe55cc677055ede662af6723b5c1615ad539b8a4bd279bdb\nfc9269fb2e651cd4a32b65ae164f79b0a2ea823e0a83508c85d7985a6bed43cf\nfd8065bcb1afdbed19e028465d5d00cd2ecadc4558de05c6fa28bea3c817aa22\nfdda64c47361b0d1a146e5b7b48dc6b7de615ea80b31f01227a3b16469589528\nfe80a2cf3c93dafad8c364fdd1646b0ba4db056cdb7bdb81474f957064812bba\nfec226e45f49ab81ab71e0eaa1248ba09b56a328338dce93a43f4044eababed5\nfeffce59a1a3eb0a6a05992bb7423c39c7d52865846da36d89e2a72c379e5398\nff3407842ada5bc18be79ae453e5bdaa1b68afc842fc22fa618ac6e6599d0bb3\nff3e512b5fb860e5855d0c05b6cf5a6bcc7792e4be1f0bdab5a00af0e18435c0\nff599c7301daa1f783924ac8cbe3ce7b42878f15a39c2d19659189951f540f48\n"
  },
  {
    "path": "kaggle_dsb18/kaggle_dsb18_preprocessing.py",
    "content": "import os\nimport numpy as np\n\nfrom skimage import io, img_as_ubyte\n\nfrom shutil import copy\nfrom collections import Container\nfrom argparse import ArgumentParser\n\n\n# I would like to take a minute to express that relative imports in Python are horrible.\n# Although this function is implemented somewhere else, it cannot be imported, since its\n# folder is in the parent folder of this. Relative imports result in ValueErrors. The\n# design choice behind this decision eludes me. The only way to circumvent this is either\n# make this package installable, add the parent folder to PATH or implement it again.\n# I went with the latter one.\n#\n# If you are reading this and you also hate the relative imports in Python, cheers!\n# You are not alone.\ndef chk_mkdir(*paths: Container) -> None:\n    \"\"\"\n    Creates folders if they do not exist.\n\n    Args:\n        paths: Container of paths to be created.\n    \"\"\"\n    for path in paths:\n        if not os.path.exists(path):\n            os.makedirs(path)\n\n\ndef merge_masks(masks_folder):\n    masks = list()\n    for mask_img_filename in os.listdir(masks_folder):\n        mask_img = io.imread(os.path.join(masks_folder, mask_img_filename))\n        masks.append(mask_img)\n\n    merged_mask = np.sum(masks, axis=0)\n    merged_mask[merged_mask > 0] = 1\n\n    return img_as_ubyte(merged_mask)\n\n\nif __name__ == '__main__':\n\n    parser = ArgumentParser()\n    parser.add_argument('--dataset_path', required=True, type=str)\n    parser.add_argument('--export_path', required=True, type=str)\n    args = parser.parse_args()\n\n    new_images_folder = os.path.join(args.export_path, 'images')\n    new_masks_folder = os.path.join(args.export_path, 'masks')\n\n    chk_mkdir(args.export_path, new_images_folder, new_masks_folder)\n\n    for image_name in os.listdir(args.dataset_path):\n        images_folder = os.path.join(args.dataset_path, image_name, 'images')\n        masks_folder = os.path.join(args.dataset_path, image_name, 'masks')\n        # copy the image\n        copy(src=os.path.join(images_folder, image_name + '.png'),\n             dst=os.path.join(new_images_folder, image_name + '.png'))\n\n        # convert and save the masks\n        mask_img = merge_masks(masks_folder)\n        io.imsave(os.path.join(new_masks_folder, image_name + '.png'), mask_img)\n"
  },
  {
    "path": "kaggle_dsb18/tissue.txt",
    "content": "00ae65c1c6631ae6f2be1a449902976e6eb8483bf6b0740d00530220832c6d3e\n0121d6759c5adb290c8e828fc882f37dfaf3663ec885c663859948c154a443ed\n01d44a26f6680c42ba94c9bc6339228579a95d0e2695b149b7cc0c9592b21baf\n0bf33d3db4282d918ec3da7112d0bf0427d4eafe74b3ee0bb419770eefe8d7d6\n0c2550a23b8a0f29a7575de8c61690d3c31bc897dd5ba66caec201d201a278c2\n0d3640c1f1b80f24e94cc9a5f3e1d9e8db7bf6af7d4aba920265f46cadc25e37\n0e21d7b3eea8cdbbed60d51d72f4f8c1974c5d76a8a3893a7d5835c85284132e\n0e4c2e2780de7ec4312f0efcd86b07c3738d21df30bb4643659962b4da5505a3\n136000dc18fa6def2d6c98d4d0b2084d13c22eaffe82e26c665bcaa2a9e51261\n13f2bec0a24c70345372febb14c4352877b1b6c1b01896246048e83c345c0914\n15039b3acccc4257a1a442646a89b6e596b5eb4531637e6d8fa1c43203722c99\n1631352dbafb8a90f11219fffd3bea368a30bc3bad3bbe0e84e19bd720df4945\n1d02c4b5921e916b9ddfb2f741fd6cf8d0e571ad51eb20e021c826b5fb87350e\n1e488c42eb1a54a3e8412b1f12cde530f950f238d71078f2ede6a85a02168e1f\n1ec74a26e772966df764e063f1391109a60d803cff9d15680093641ed691bf72\n2246874c8b5ba218d01ad8153a201ad4660195f3e4c65da6b9d4ccaf82cb7edf\n2255d5aba044256bb92f6b7cbed0fca46d972c7b6b1a59dcbe7f682c5777d074\n24a20f2a529cede5695df2422a3986505b5826bb10b10781d6db2074cf3de7b3\n2c61fdcb36fd1b2944895af6204279e9f6c164ba894198b40c8b7a3c9bf500ea\n2dd3356f2dcf470aec4003800744dfec6490e75d88011e1d835f4f3d60f88e7a\n2e2d29fc44444a85049b162eb359a523dec108ccd5bd75022b25547491abf0c7\n337b6eed0726f07531cd467cd62b6676c31a8c9e716bdbc49433986c022252cf\n33a5b0ff232b425796ee6a9dd5b516ff9aad54ca723b4ec490bf5cd9b2e2a731\n353ab00e964f71aa720385223a9078b770b7e3efaf5be0f66e670981f68fe606\n3934a094e8537841e973342c7f8880606f7a2712b14930340d6f6c2afe178c25\n3b0709483b1e86449cc355bb797e841117ba178c6ae1ed955384f4da6486aa20\n3bfa8b3b01fd24a28477f103063d17368a7398b27331e020f3a0ef59bf68c940\n40946065f7e4b6038599fbfd419f2a67e7635b6f89db3ed6c0d67c8801521af1\n4193474b2f1c72f735b13633b219d9cabdd43c21d9c2bb4dfc4809f104ba4c06\n420f43d21dbaba42bf8c0995b3a2c85537876d594433770c6c6f3d6b779ec15f\n442c4eb0185698fe7d148c108a46f74abd399aecda2f4f22981a1671cd95dd7d\n4590d7d47f521df62f3bcb0bf74d1bca861d94ade614d8afc912d1009d607b94\n45f059cf21d85ecfce0eb93260516f1e2443d210e9a52f9ae2271d604aa3fcc5\n4ae4f936a9ade472764dad80f60f7168e4be067aa66ce9d06d60ebe34951dca4\n4ca5081854df7bbcaa4934fcf34318f82733a0f8c05b942c2265eea75419d62f\n4cbd6c37f3a55a538d759d440344c287cac66260d3047a83f429e63e7a0f7f20\n4d14a3629b6af6de86d850be236b833a7bfcbf6d8665fd73c6dc339e06c14607\n4d4f254f3b8b4408d661df3735591554b2f6587ce1952928d619b48010d55467\n4e23ecf65040f86420e0201134e538951acdeda84fbb274311f995682044dd64\n4e92129f4e8066d6f560d6022cd343a2245939aa49d8b06cddbd9bfc7e7eeb0e\n52a6b8ae4c8e0a8a07a31b8e3f401d8811bf1942969c198e51dfcbd98520aa60\n55f98f43c152aa0dc8bea513f8ba558cc57494b81ae4ee816977816e79629c50\n57bd029b19c1b382bef9db3ac14f13ea85e36a6053b92e46caedee95c05847ab\n589f86dee5b480a88dd4f77eeaffe2c4d70aefdf879a4096dde1fa4d41055b8f\n5c6eb9a47852754d4e45eceb9a696c64c7cfe304afc5ea491cdfef11d55c17f3\n5d75a63972ef643efd7c42f20668b167f2af43635d6263962d84e62e7609ab51\n5ef4442e5b8b0b4cf824b61be4050dfd793d846e0a6800afa4425a2f66e91456\n610f32e2d9d270d740aec501dcf0c89595e4e623468ad43272adab90520a8f96\n65c8527c16a016191118e8adc3d307fe3a73d37cbe05597a95aebd75daf8d051\n673baf65ae5c571d6be452eb41e79ef3fc2eb3fd238e621c6b7621763b429989\n6ab24e7e1f6c9fdd371c5edae1bbb20abeeb976811f8ab2375880b4483860f4d\n6b0ac2ab04c09dced54058ec504a4947f8ecd5727dfca7e0b3f69de71d0d31c7\n6c67b78e8164801059375ed9a607f61e67a7ae347e92e36a7f20514224541d56\n6d327ab4f0e3604fa6e9b8041c7e6db86ab809890d886c691f6e59c9168b7fbe\n708eb41a3fc8f2b6cd1f529cdf38dc4ad5d5f00ad30bdcba92884f37ff78d614\n709e094e39629a9ca21e187f007b331074694e443db40289447c1111f7e267e7\n718751b439c05bdd589f04fcef321a86be3ecb35292a435138e295e05eb2e771\n74a7785530687a11ecd073e772f90912d9967d02407a192bfab282c35f55ab94\n7773ac91af61ed041701b7c3b649598e3707cf04c0577f464fd31be687f538fe\n7798ca1ddb3133563e290c36228bc8f8f3c9f224e096f442ef0653856662d121\n79fe419488ba98494e3baa35c6fef9662eda1efe325d0ab0ac002f5383245d96\n7f34dfccd1bc2e2466ee3d6f74ff05821a0e5404e9cf2c9568da26b59f7afda5\n7f55678298adb736987d9fb5d1d2daefb08fe5bf4d81b2380bedf9449f79cc38\n815524d88283ba10ad597b87aa1967671db776df8004a0c4291b67fc2624c22a\n853a4c67900c411abd04467f7bc7813d3c58a5f565c8b0807e13c6e6dea21344\n87ea72894f6534b28e740cc34cf5c9eb75d0d8902687fce5fcc08a92e9f41386\n8e507d58f4c27cd2a82bee79fe27b069befd62a46fdaed20970a95a2ba819c7b\n8f27ebc74164eddfe989a98a754dcf5a9c85ef599a1321de24bcf097df1814ca\n92f31f591929a30e4309ab75185c96ff4314ce0a7ead2ed2c2171897ad1da0c7\n94519eb45cbe1573252623b7ea06a8b43c19c930f5c9b685edb639d0db719ab0\n947c0d94c8213ac7aaa41c4efc95d854246550298259cf1bb489654d0e969050\n953211bcc0192e2298087d30e708dba68def9e0c13a3ff3326a18b0962c63adc\n9fb32aba1c2fd53273dca9abefac944ba747f578da82dfaa1249f332a2324944\na0325cb7aa59e9c0a75e64ba26855d8032c46161aa4bca0c01bac5e4a836485e\na08166d91d2cca263d2dd52764dc25c9c582b7a5ece2b802749fa4be33187c49\na31deaf0ac279d5f34fb2eca80cc2abce6ef30bd64e7aca40efe4b2ba8e9ad3d\na4c44fc5f5bf213e2be6091ccaed49d8bf039d78f6fbd9c4d7b7428cfcb2eda4\na5fe0b7412dd152c41f7afc34ffdf276d4261b6942fa6d36803648e90f2cfc06\na7f6194ddbeaefb1da571226a97785d09ccafc5893ce3c77078d2040bccfcb77\na7f767ca9770b160f234780e172aeb35a50830ba10dc49c526f4712451abe1d2\na90401357d50e1376354ae6e5f56a2e4dff3fdb5a4e8d50316673b2b8f1f293b\naa58ba4512955771b4f9b459cb4e6a8adb71d11cd6cae662ec2df31d688a5fe0\nabd8dde78f8d37b68b28da67459371ed65f0a575523e94bc4ecbc88e6fedf0d0\nad473063dab4bf4f2461d9a99a9c0166d4871f156516d9e0a523484e7cf2258d\nae570a676961482848b5097038ef5e407df7a66a8e1c9b0567da599565a6b142\naf576e8ec3a8d0b57eb6a311299e9e4fd2047970d3dd9d6f52e54ea6a91109da\naf6b6173c59450bc76b2cc461cf233921fbfdb6feb8dd6da03a0d44193221fd0\nb0e35e06b85da49bfe3ea737711a72b551a6add446e30eabb01aa683a79873c5\nb214800de5ed4cc558f44d569495970f93c8c047f8e464c51d4bd5c276118423\nb909aa8f6f4bec37c3fb6ff5a85d166162d07983506fcc57be742b0f9dbafbf7\nbb61fc17daf8bdd4e16fdcf50137a8d7762bec486ede9249d92e511fcb693676\nbe771d6831e3f8f1af4696bc08a582f163735db5baf9906e4729acc6a05e1187\nbf4a61bb81589c9a67e3343408befd3e135af5e88b50c17f998f2131d24bc271\nc0152b1a260e71f9823d17f4fbb4bf7020d5dce62b4a12b3099c1c8e52a1c43a\nc0f172831b8017c769ff0e80f85b096ac939e79de3d524e0826fbb95221365da\nc1afe66cd139f996fd984f5f2622903730ec2f1192d90608154f07f7ef6cdb4b\ncbca32daaae36a872a11da4eaff65d1068ff3f154eedc9d3fc0c214a4e5d32bd\ncbff60361ded0570e5d50429a1aa51d81471819bc9b38359f03cfef76de0038c\ne414b54f2036bcab61b9c0a966f65adf4b169097c13c740e03d6292ac076258c\ne49fc2b4f1f39d481a6525225ab3f688be5c87f56884456ad54c953315efae83\nea94ba4b01d1bd5f7768d10e0ac547743791033df545c71fcec442d0cb5cb5e7\neb1df8ed879d04b36980b0958a0e8fc446ad08c0bdcf3b5f42e3db023187c7e5\nebc18868864ad075548cc1784f4f9a237bb98335f9645ee727dac8332a3e3716\ned5be4b63e9506ad64660dd92a098ffcc0325195298c13c815a73773f1efc279\nef3ef194e5657fda708ecbd3eb6530286ed2ba23c88efb9f1715298975c73548\nf4b7c24baf69b8752c49d0eb5db4b7b5e1524945d48e54925bff401d5658045d\nf7e5dcfc9c93183c668c5a4ab028d5faad54fb54298711f2caae0508aa978300\nf952cc65376009cfad8249e53b9b2c0daaa3553e897096337d143c625c2df886\n"
  },
  {
    "path": "predict.py",
    "content": "import os\n\nfrom argparse import ArgumentParser\n\nfrom unet.model import Model\nfrom unet.dataset import Image2D\n\nparser = ArgumentParser()\nparser.add_argument('--dataset', required=True, type=str)\nparser.add_argument('--results_path', required=True, type=str)\nparser.add_argument('--model_path', required=True, type=str)\nparser.add_argument('--device', default='cpu', type=str)\nargs = parser.parse_args()\n\npredict_dataset = Image2D(args.dataset)\nmodel = torch.load(args.model_path)\n\nif not os.path.exists(args.results_path):\n    os.makedirs(args.results_path)\n\nmodel = Model(unet, checkpoint_folder=args.results_path, device=args.device)\n\nmodel.predict_dataset(predict_dataset, args.result_path)"
  },
  {
    "path": "train.py",
    "content": "import os\n\nimport torch.optim as optim\n\nfrom functools import partial\nfrom argparse import ArgumentParser\n\nfrom unet.unet import UNet2D\nfrom unet.model import Model\nfrom unet.utils import MetricList\nfrom unet.metrics import jaccard_index, f1_score, LogNLLLoss\nfrom unet.dataset import JointTransform2D, ImageToImage2D, Image2D\n\nparser = ArgumentParser()\nparser.add_argument('--train_dataset', required=True, type=str)\nparser.add_argument('--val_dataset', type=str)\nparser.add_argument('--checkpoint_path', required=True, type=str)\nparser.add_argument('--device', default='cpu', type=str)\nparser.add_argument('--in_channels', default=3, type=int)\nparser.add_argument('--out_channels', default=2, type=int)\nparser.add_argument('--depth', default=5, type=int)\nparser.add_argument('--width', default=32, type=int)\nparser.add_argument('--epochs', default=100, type=int)\nparser.add_argument('--batch_size', default=1, type=int)\nparser.add_argument('--save_freq', default=0, type=int)\nparser.add_argument('--save_model', default=0, type=int)\nparser.add_argument('--model_name', type=str, default='model')\nparser.add_argument('--learning_rate', type=float, default=1e-3)\nparser.add_argument('--crop', type=int, default=None)\nargs = parser.parse_args()\n\nif args.crop is not None:\n    crop = (args.crop, args.crop)\nelse:\n    crop = None\n\ntf_train = JointTransform2D(crop=crop, p_flip=0.5, color_jitter_params=None, long_mask=True)\ntf_val = JointTransform2D(crop=crop, p_flip=0, color_jitter_params=None, long_mask=True)\ntrain_dataset = ImageToImage2D(args.train_dataset, tf_val)\nval_dataset = ImageToImage2D(args.val_dataset, tf_val)\npredict_dataset = Image2D(args.val_dataset)\n\nconv_depths = [int(args.width*(2**k)) for k in range(args.depth)]\nunet = UNet2D(args.in_channels, args.out_channels, conv_depths)\nloss = LogNLLLoss()\noptimizer = optim.Adam(unet.parameters(), lr=args.learning_rate)\n\nresults_folder = os.path.join(args.checkpoint_path, args.model_name)\nif not os.path.exists(results_folder):\n    os.makedirs(results_folder)\n\nmetric_list = MetricList({'jaccard': partial(jaccard_index),\n                          'f1': partial(f1_score)})\n\nmodel = Model(unet, loss, optimizer, results_folder, device=args.device)\n\nmodel.fit_dataset(train_dataset, n_epochs=args.epochs, n_batch=args.batch_size,\n                  shuffle=True, val_dataset=val_dataset, save_freq=args.save_freq,\n                  save_model=args.save_model, predict_dataset=predict_dataset,\n                  metric_list=metric_list, verbose=True)\n"
  },
  {
    "path": "unet/__init__.py",
    "content": ""
  },
  {
    "path": "unet/blocks.py",
    "content": "import torch.nn as nn\nfrom torch.nn.modules.loss import _Loss\n\n\nclass SoftDiceLoss(_Loss):\n    def __init__(self, size_average=None, reduce=None, reduction='mean'):\n        super(SoftDiceLoss, self).__init__(size_average, reduce, reduction)\n\n    def forward(self, y_pred, y_gt):\n        numerator = torch.sum(y_pred*y_gt)\n        denominator = torch.sum(y_pred*y_pred + y_gt*y_gt)\n        return numerator/denominator\n\n\nclass First2D(nn.Module):\n    def __init__(self, in_channels, middle_channels, out_channels, dropout=False):\n        super(First2D, self).__init__()\n\n        layers = [\n            nn.Conv2d(in_channels, middle_channels, kernel_size=3, padding=1),\n            nn.BatchNorm2d(middle_channels),\n            nn.ReLU(inplace=True),\n            nn.Conv2d(middle_channels, out_channels, kernel_size=3, padding=1),\n            nn.BatchNorm2d(out_channels),\n            nn.ReLU(inplace=True)\n        ]\n\n        if dropout:\n            assert 0 <= dropout <= 1, 'dropout must be between 0 and 1'\n            layers.append(nn.Dropout2d(p=dropout))\n\n        self.first = nn.Sequential(*layers)\n\n    def forward(self, x):\n        return self.first(x)\n\n\nclass Encoder2D(nn.Module):\n    def __init__(\n            self, in_channels, middle_channels, out_channels,\n            dropout=False, downsample_kernel=2\n    ):\n        super(Encoder2D, self).__init__()\n\n        layers = [\n            nn.MaxPool2d(kernel_size=downsample_kernel),\n            nn.Conv2d(in_channels, middle_channels, kernel_size=3, padding=1),\n            nn.BatchNorm2d(middle_channels),\n            nn.ReLU(inplace=True),\n            nn.Conv2d(middle_channels, out_channels, kernel_size=3, padding=1),\n            nn.BatchNorm2d(out_channels),\n            nn.ReLU(inplace=True)\n        ]\n\n        if dropout:\n            assert 0 <= dropout <= 1, 'dropout must be between 0 and 1'\n            layers.append(nn.Dropout2d(p=dropout))\n\n        self.encoder = nn.Sequential(*layers)\n\n    def forward(self, x):\n        return self.encoder(x)\n\n\nclass Center2D(nn.Module):\n    def __init__(self, in_channels, middle_channels, out_channels, deconv_channels, dropout=False):\n        super(Center2D, self).__init__()\n\n        layers = [\n            nn.MaxPool2d(kernel_size=2),\n            nn.Conv2d(in_channels, middle_channels, kernel_size=3, padding=1),\n            nn.BatchNorm2d(middle_channels),\n            nn.ReLU(inplace=True),\n            nn.Conv2d(middle_channels, out_channels, kernel_size=3, padding=1),\n            nn.BatchNorm2d(out_channels),\n            nn.ReLU(inplace=True),\n            nn.ConvTranspose2d(out_channels, deconv_channels, kernel_size=2, stride=2)\n        ]\n\n        if dropout:\n            assert 0 <= dropout <= 1, 'dropout must be between 0 and 1'\n            layers.append(nn.Dropout2d(p=dropout))\n\n        self.center = nn.Sequential(*layers)\n\n    def forward(self, x):\n        return self.center(x)\n\n\nclass Decoder2D(nn.Module):\n    def __init__(self, in_channels, middle_channels, out_channels, deconv_channels, dropout=False):\n        super(Decoder2D, self).__init__()\n\n        layers = [\n            nn.Conv2d(in_channels, middle_channels, kernel_size=3, padding=1),\n            nn.BatchNorm2d(middle_channels),\n            nn.ReLU(inplace=True),\n            nn.Conv2d(middle_channels, out_channels, kernel_size=3, padding=1),\n            nn.BatchNorm2d(out_channels),\n            nn.ReLU(inplace=True),\n            nn.ConvTranspose2d(out_channels, deconv_channels, kernel_size=2, stride=2)\n        ]\n\n        if dropout:\n            assert 0 <= dropout <= 1, 'dropout must be between 0 and 1'\n            layers.append(nn.Dropout2d(p=dropout))\n\n        self.decoder = nn.Sequential(*layers)\n\n    def forward(self, x):\n        return self.decoder(x)\n\n\nclass Last2D(nn.Module):\n    def __init__(self, in_channels, middle_channels, out_channels, softmax=False):\n        super(Last2D, self).__init__()\n\n        layers = [\n            nn.Conv2d(in_channels, middle_channels, kernel_size=3, padding=1),\n            nn.BatchNorm2d(middle_channels),\n            nn.ReLU(inplace=True),\n            nn.Conv2d(middle_channels, middle_channels, kernel_size=3, padding=1),\n            nn.BatchNorm2d(middle_channels),\n            nn.ReLU(inplace=True),\n            nn.Conv2d(middle_channels, out_channels, kernel_size=1),\n            nn.Softmax(dim=1)\n        ]\n\n        self.first = nn.Sequential(*layers)\n\n    def forward(self, x):\n        return self.first(x)\n\n\nclass First3D(nn.Module):\n    def __init__(self, in_channels, middle_channels, out_channels, dropout=False):\n        super(First3D, self).__init__()\n\n        layers = [\n            nn.Conv3d(in_channels, middle_channels, kernel_size=3, padding=1),\n            nn.BatchNorm3d(middle_channels),\n            nn.ReLU(inplace=True),\n            nn.Conv3d(middle_channels, out_channels, kernel_size=3, padding=1),\n            nn.BatchNorm3d(out_channels),\n            nn.ReLU(inplace=True)\n        ]\n\n        if dropout:\n            assert 0 <= dropout <= 1, 'dropout must be between 0 and 1'\n            layers.append(nn.Dropout3d(p=dropout))\n\n        self.first = nn.Sequential(*layers)\n\n    def forward(self, x):\n        return self.first(x)\n\n\nclass Encoder3D(nn.Module):\n    def __init__(\n            self, in_channels, middle_channels, out_channels,\n            dropout=False, downsample_kernel=2\n    ):\n        super(Encoder3D, self).__init__()\n\n        layers = [\n            nn.MaxPool3d(kernel_size=downsample_kernel),\n            nn.Conv3d(in_channels, middle_channels, kernel_size=3, padding=1),\n            nn.BatchNorm3d(middle_channels),\n            nn.ReLU(inplace=True),\n            nn.Conv3d(middle_channels, out_channels, kernel_size=3, padding=1),\n            nn.BatchNorm3d(out_channels),\n            nn.ReLU(inplace=True)\n        ]\n\n        if dropout:\n            assert 0 <= dropout <= 1, 'dropout must be between 0 and 1'\n            layers.append(nn.Dropout3d(p=dropout))\n\n        self.encoder = nn.Sequential(*layers)\n\n    def forward(self, x):\n        return self.encoder(x)\n\n\nclass Center3D(nn.Module):\n    def __init__(self, in_channels, middle_channels, out_channels, deconv_channels, dropout=False):\n        super(Center3D, self).__init__()\n\n        layers = [\n            nn.MaxPool3d(kernel_size=2),\n            nn.Conv3d(in_channels, middle_channels, kernel_size=3, padding=1),\n            nn.BatchNorm3d(middle_channels),\n            nn.ReLU(inplace=True),\n            nn.Conv3d(middle_channels, out_channels, kernel_size=3, padding=1),\n            nn.BatchNorm3d(out_channels),\n            nn.ReLU(inplace=True),\n            nn.ConvTranspose3d(out_channels, deconv_channels, kernel_size=2, stride=2)\n        ]\n\n        if dropout:\n            assert 0 <= dropout <= 1, 'dropout must be between 0 and 1'\n            layers.append(nn.Dropout3d(p=dropout))\n\n        self.center = nn.Sequential(*layers)\n\n    def forward(self, x):\n        return self.center(x)\n\n\nclass Decoder3D(nn.Module):\n    def __init__(self, in_channels, middle_channels, out_channels, deconv_channels, dropout=False):\n        super(Decoder3D, self).__init__()\n\n        layers = [\n            nn.Conv3d(in_channels, middle_channels, kernel_size=3, padding=1),\n            nn.BatchNorm3d(middle_channels),\n            nn.ReLU(inplace=True),\n            nn.Conv3d(middle_channels, out_channels, kernel_size=3, padding=1),\n            nn.BatchNorm3d(out_channels),\n            nn.ReLU(inplace=True),\n            nn.ConvTranspose3d(out_channels, deconv_channels, kernel_size=2, stride=2)\n        ]\n\n        if dropout:\n            assert 0 <= dropout <= 1, 'dropout must be between 0 and 1'\n            layers.append(nn.Dropout3d(p=dropout))\n\n        self.decoder = nn.Sequential(*layers)\n\n    def forward(self, x):\n        return self.decoder(x)\n\n\nclass Last3D(nn.Module):\n    def __init__(self, in_channels, middle_channels, out_channels, softmax=False):\n        super(Last3D, self).__init__()\n\n        layers = [\n            nn.Conv3d(in_channels, middle_channels, kernel_size=3, padding=1),\n            nn.BatchNorm3d(middle_channels),\n            nn.ReLU(inplace=True),\n            nn.Conv3d(middle_channels, middle_channels, kernel_size=3, padding=1),\n            nn.BatchNorm3d(middle_channels),\n            nn.ReLU(inplace=True),\n            nn.Conv3d(middle_channels, out_channels, kernel_size=1),\n            nn.Softmax(dim=1)\n        ]\n\n        self.first = nn.Sequential(*layers)\n\n    def forward(self, x):\n        return self.first(x)"
  },
  {
    "path": "unet/dataset.py",
    "content": "import os\nimport numpy as np\nimport torch\n\nfrom skimage import io\n\nfrom torch.utils.data import Dataset\nfrom torchvision import transforms as T\nfrom torchvision.transforms import functional as F\n\nfrom typing import Callable\n\n\ndef to_long_tensor(pic):\n    # handle numpy array\n    img = torch.from_numpy(np.array(pic, np.uint8))\n    # backward compatibility\n    return img.long()\n\n\ndef correct_dims(*images):\n    corr_images = []\n    for img in images:\n        if len(img.shape) == 2:\n            corr_images.append(np.expand_dims(img, axis=2))\n        else:\n            corr_images.append(img)\n\n    if len(corr_images) == 1:\n        return corr_images[0]\n    else:\n        return corr_images\n\n\nclass JointTransform2D:\n    \"\"\"\n    Performs augmentation on image and mask when called. Due to the randomness of augmentation transforms,\n    it is not enough to simply apply the same Transform from torchvision on the image and mask separetely.\n    Doing this will result in messing up the ground truth mask. To circumvent this problem, this class can\n    be used, which will take care of the problems above.\n\n    Args:\n        crop: tuple describing the size of the random crop. If bool(crop) evaluates to False, no crop will\n            be taken.\n        p_flip: float, the probability of performing a random horizontal flip.\n        color_jitter_params: tuple describing the parameters of torchvision.transforms.ColorJitter.\n            If bool(color_jitter_params) evaluates to false, no color jitter transformation will be used.\n        p_random_affine: float, the probability of performing a random affine transform using\n            torchvision.transforms.RandomAffine.\n        long_mask: bool, if True, returns the mask as LongTensor in label-encoded format.\n    \"\"\"\n    def __init__(self, crop=(256, 256), p_flip=0.5, color_jitter_params=(0.1, 0.1, 0.1, 0.1),\n                 p_random_affine=0, long_mask=False):\n        self.crop = crop\n        self.p_flip = p_flip\n        self.color_jitter_params = color_jitter_params\n        if color_jitter_params:\n            self.color_tf = T.ColorJitter(*color_jitter_params)\n        self.p_random_affine = p_random_affine\n        self.long_mask = long_mask\n\n    def __call__(self, image, mask):\n        # transforming to PIL image\n        image, mask = F.to_pil_image(image), F.to_pil_image(mask)\n\n        # random crop\n        if self.crop:\n            i, j, h, w = T.RandomCrop.get_params(image, self.crop)\n            image, mask = F.crop(image, i, j, h, w), F.crop(mask, i, j, h, w)\n\n        if np.random.rand() < self.p_flip:\n            image, mask = F.hflip(image), F.hflip(mask)\n\n        # color transforms || ONLY ON IMAGE\n        if self.color_jitter_params:\n            image = self.color_tf(image)\n\n        # random affine transform\n        if np.random.rand() < self.p_random_affine:\n            affine_params = T.RandomAffine(180).get_params((-90, 90), (1, 1), (2, 2), (-45, 45), self.crop)\n            image, mask = F.affine(image, *affine_params), F.affine(mask, *affine_params)\n\n        # transforming to tensor\n        image = F.to_tensor(image)\n        if not self.long_mask:\n            mask = F.to_tensor(mask)\n        else:\n            mask = to_long_tensor(mask)\n\n        return image, mask\n\n\nclass ImageToImage2D(Dataset):\n    \"\"\"\n    Reads the images and applies the augmentation transform on them.\n    Usage:\n        1. If used without the unet.model.Model wrapper, an instance of this object should be passed to\n           torch.utils.data.DataLoader. Iterating through this returns the tuple of image, mask and image\n           filename.\n        2. With unet.model.Model wrapper, an instance of this object should be passed as train or validation\n           datasets.\n\n    Args:\n        dataset_path: path to the dataset. Structure of the dataset should be:\n            dataset_path\n              |-- images\n                  |-- img001.png\n                  |-- img002.png\n                  |-- ...\n              |-- masks\n                  |-- img001.png\n                  |-- img002.png\n                  |-- ...\n\n        joint_transform: augmentation transform, an instance of JointTransform2D. If bool(joint_transform)\n            evaluates to False, torchvision.transforms.ToTensor will be used on both image and mask.\n        one_hot_mask: bool, if True, returns the mask in one-hot encoded form.\n    \"\"\"\n\n    def __init__(self, dataset_path: str, joint_transform: Callable = None, one_hot_mask: int = False) -> None:\n        self.dataset_path = dataset_path\n        self.input_path = os.path.join(dataset_path, 'images')\n        self.output_path = os.path.join(dataset_path, 'masks')\n        self.images_list = os.listdir(self.input_path)\n        self.one_hot_mask = one_hot_mask\n\n        if joint_transform:\n            self.joint_transform = joint_transform\n        else:\n            to_tensor = T.ToTensor()\n            self.joint_transform = lambda x, y: (to_tensor(x), to_tensor(y))\n\n    def __len__(self):\n        return len(os.listdir(self.input_path))\n\n    def __getitem__(self, idx):\n        image_filename = self.images_list[idx]\n        # read image\n        image = io.imread(os.path.join(self.input_path, image_filename))\n        # read mask image\n        mask = io.imread(os.path.join(self.output_path, image_filename))\n\n        # correct dimensions if needed\n        image, mask = correct_dims(image, mask)\n\n        if self.joint_transform:\n            image, mask = self.joint_transform(image, mask)\n\n        if self.one_hot_mask:\n            assert self.one_hot_mask > 0, 'one_hot_mask must be nonnegative'\n            mask = torch.zeros((self.one_hot_mask, mask.shape[1], mask.shape[2])).scatter_(0, mask.long(), 1)\n\n        return image, mask, image_filename\n\n\nclass Image2D(Dataset):\n    \"\"\"\n    Reads the images and applies the augmentation transform on them. As opposed to ImageToImage2D, this\n    reads a single image and requires a simple augmentation transform.\n    Usage:\n        1. If used without the unet.model.Model wrapper, an instance of this object should be passed to\n           torch.utils.data.DataLoader. Iterating through this returns the tuple of image and image\n           filename.\n        2. With unet.model.Model wrapper, an instance of this object should be passed as a prediction\n           dataset.\n\n    Args:\n        dataset_path: path to the dataset. Structure of the dataset should be:\n            dataset_path\n              |-- images\n                  |-- img001.png\n                  |-- img002.png\n                  |-- ...\n\n        transform: augmentation transform. If bool(joint_transform) evaluates to False,\n            torchvision.transforms.ToTensor will be used.\n    \"\"\"\n\n    def __init__(self, dataset_path: str, transform: Callable = None):\n        self.dataset_path = dataset_path\n        self.input_path = os.path.join(dataset_path, 'images')\n        self.images_list = os.listdir(self.input_path)\n\n        if transform:\n            self.transform = transform\n        else:\n            self.transform = T.ToTensor()\n\n    def __len__(self):\n        return len(os.listdir(self.input_path))\n\n    def __getitem__(self, idx):\n        image_filename = self.images_list[idx]\n        image = io.imread(os.path.join(self.input_path, image_filename))\n\n        # correct dimensions if needed\n        image = correct_dims(image)\n\n        image = self.transform(image)\n\n        return image, image_filename\n"
  },
  {
    "path": "unet/metrics.py",
    "content": "import torch\nfrom torch.nn.functional import cross_entropy\nfrom torch.nn.modules.loss import _WeightedLoss\n\n\nEPSILON = 1e-32\n\n\nclass LogNLLLoss(_WeightedLoss):\n    __constants__ = ['weight', 'reduction', 'ignore_index']\n\n    def __init__(self, weight=None, size_average=None, reduce=None, reduction=None,\n                 ignore_index=-100):\n        super(LogNLLLoss, self).__init__(weight, size_average, reduce, reduction)\n        self.ignore_index = ignore_index\n\n    def forward(self, y_input, y_target):\n        y_input = torch.log(y_input + EPSILON)\n        return cross_entropy(y_input, y_target, weight=self.weight,\n                             ignore_index=self.ignore_index)\n\n\ndef classwise_iou(output, gt):\n    \"\"\"\n    Args:\n        output: torch.Tensor of shape (n_batch, n_classes, image.shape)\n        gt: torch.LongTensor of shape (n_batch, image.shape)\n    \"\"\"\n    dims = (0, *range(2, len(output.shape)))\n    gt = torch.zeros_like(output).scatter_(1, gt[:, None, :], 1)\n    intersection = output*gt\n    union = output + gt - intersection\n    classwise_iou = (intersection.sum(dim=dims).float() + EPSILON) / (union.sum(dim=dims) + EPSILON)\n\n    return classwise_iou\n\n\ndef classwise_f1(output, gt):\n    \"\"\"\n    Args:\n        output: torch.Tensor of shape (n_batch, n_classes, image.shape)\n        gt: torch.LongTensor of shape (n_batch, image.shape)\n    \"\"\"\n\n    epsilon = 1e-20\n    n_classes = output.shape[1]\n\n    output = torch.argmax(output, dim=1)\n    true_positives = torch.tensor([((output == i) * (gt == i)).sum() for i in range(n_classes)]).float()\n    selected = torch.tensor([(output == i).sum() for i in range(n_classes)]).float()\n    relevant = torch.tensor([(gt == i).sum() for i in range(n_classes)]).float()\n\n    precision = (true_positives + epsilon) / (selected + epsilon)\n    recall = (true_positives + epsilon) / (relevant + epsilon)\n    classwise_f1 = 2 * (precision * recall) / (precision + recall)\n\n    return classwise_f1\n\n\ndef make_weighted_metric(classwise_metric):\n    \"\"\"\n    Args:\n        classwise_metric: classwise metric like classwise_IOU or classwise_F1\n    \"\"\"\n\n    def weighted_metric(output, gt, weights=None):\n\n        # dimensions to sum over\n        dims = (0, *range(2, len(output.shape)))\n\n        # default weights\n        if weights == None:\n            weights = torch.ones(output.shape[1]) / output.shape[1]\n        else:\n            # creating tensor if needed\n            if len(weights) != output.shape[1]:\n                raise ValueError(\"The number of weights must match with the number of classes\")\n            if not isinstance(weights, torch.Tensor):\n                weights = torch.tensor(weights)\n            # normalizing weights\n            weights /= torch.sum(weights)\n\n        classwise_scores = classwise_metric(output, gt).cpu()\n\n        return (classwise_scores * weights).sum().item()\n\n    return weighted_metric\n\n\njaccard_index = make_weighted_metric(classwise_iou)\nf1_score = make_weighted_metric(classwise_f1)\n\n\nif __name__ == '__main__':\n    output, gt = torch.zeros(3, 2, 5, 5), torch.zeros(3, 5, 5).long()\n    print(classwise_iou(output, gt))\n"
  },
  {
    "path": "unet/model.py",
    "content": "import os\n\nimport numpy as np\nimport torch\nimport torch.nn as nn\n\nfrom torch.autograd import Variable\nfrom torch.utils.data import DataLoader\n\nfrom skimage import io\n\nfrom time import time\n\nfrom .utils import chk_mkdir, Logger, MetricList\nfrom .dataset import ImageToImage2D, Image2D\n\n\nclass Model:\n    \"\"\"\n    Wrapper for the U-Net network. (Or basically any CNN for semantic segmentation.)\n\n    Args:\n        net: the neural network, which should be an instance of unet.unet.UNet2D\n        loss: loss function to be used during training\n        optimizer: optimizer to be used during training\n        checkpoint_folder: path to the folder where you wish to save the results\n        scheduler: learning rate scheduler (optional)\n        device: torch.device object where you would like to do the training\n            (optional, default is cpu)\n        save_model: bool, indicates whether or not you wish to save the models\n            during training (optional, default is False)\n    \"\"\"\n    def __init__(self, net: nn.Module, loss, optimizer, checkpoint_folder: str,\n                 scheduler: torch.optim.lr_scheduler._LRScheduler = None,\n                 device: torch.device = torch.device('cpu')):\n        \"\"\"\n        Wrapper for PyTorch models.\n\n        Args:\n            net: PyTorch model.\n            loss: Loss function which you would like to use during training.\n            optimizer: Optimizer for the training.\n            checkpoint_folder: Folder for saving the results and predictions.\n            scheduler: Learning rate scheduler for the optimizer. Optional.\n            device: The device on which the model and tensor should be\n                located. Optional. The default device is the cpu.\n\n        Attributes:\n            net: PyTorch model.\n            loss: Loss function which you would like to use during training.\n            optimizer: Optimizer for the training.\n            checkpoint_folder: Folder for saving the results and predictions.\n            scheduler: Learning rate scheduler for the optimizer. Optional.\n            device: The device on which the model and tensor should be\n                located. Optional.\n        \"\"\"\n        self.net = net\n        self.loss = loss\n        self.optimizer = optimizer\n        self.scheduler = scheduler\n\n        self.checkpoint_folder = checkpoint_folder\n        chk_mkdir(self.checkpoint_folder)\n\n        # moving net and loss to the selected device\n        self.device = device\n        self.net.to(device=self.device)\n        try:\n            self.loss.to(device=self.device)\n        except:\n            pass\n\n    def fit_epoch(self, dataset, n_batch=1, shuffle=False):\n        \"\"\"\n        Trains the model for one epoch on the provided dataset.\n\n        Args:\n             dataset: an instance of unet.dataset.ImageToImage2D\n             n_batch: size of batch during training\n             shuffle: bool, indicates whether or not to shuffle the dataset\n                during training\n\n        Returns:\n              logs: dictionary object containing the training loss\n        \"\"\"\n\n        self.net.train(True)\n\n        epoch_running_loss = 0\n\n        for batch_idx, (X_batch, y_batch, *rest) in enumerate(DataLoader(dataset, batch_size=n_batch, shuffle=shuffle)):\n\n            X_batch = Variable(X_batch.to(device=self.device))\n            y_batch = Variable(y_batch.to(device=self.device))\n\n            # training\n            self.optimizer.zero_grad()\n            y_out = self.net(X_batch)\n            training_loss = self.loss(y_out, y_batch)\n            training_loss.backward()\n            self.optimizer.step()\n            epoch_running_loss += training_loss.item()\n\n        self.net.train(False)\n\n        del X_batch, y_batch\n\n        logs = {'train_loss': epoch_running_loss / (batch_idx + 1)}\n\n        return logs\n\n    def val_epoch(self, dataset, n_batch=1, metric_list=MetricList({})):\n        \"\"\"\n        Validation of given dataset.\n\n        Args:\n             dataset: an instance of unet.dataset.ImageToImage2D\n             n_batch: size of batch during training\n             metric_list: unet.utils.MetricList object, which contains metrics\n                to be recorded during validation\n\n        Returns:\n            logs: dictionary object containing the validation loss and\n                the metrics given by the metric_list object\n        \"\"\"\n\n        self.net.train(False)\n        metric_list.reset()\n        running_val_loss = 0.0\n\n        for batch_idx, (X_batch, y_batch, *rest) in enumerate(DataLoader(dataset, batch_size=n_batch)):\n\n            X_batch = Variable(X_batch.to(device=self.device))\n            y_batch = Variable(y_batch.to(device=self.device))\n\n            y_out = self.net(X_batch)\n            training_loss = self.loss(y_out, y_batch)\n            running_val_loss += training_loss.item()\n            metric_list(y_out, y_batch)\n\n        del X_batch, y_batch\n\n        logs = {'val_loss': running_val_loss/(batch_idx + 1),\n                **metric_list.get_results(normalize=batch_idx+1)}\n\n        return logs\n\n    def fit_dataset(self, dataset: ImageToImage2D, n_epochs: int, n_batch: int = 1, shuffle: bool = False,\n                    val_dataset: ImageToImage2D = None, save_freq: int = 100, save_model: bool = False,\n                    predict_dataset: Image2D = None, metric_list: MetricList = MetricList({}),\n                    verbose: bool = False):\n\n        \"\"\"\n        Training loop for the network.\n\n        Args:\n            dataset: an instance of unet.dataset.ImageToImage2D\n            n_epochs: number of epochs\n            shuffle: bool indicating whether or not suffle the dataset during training\n            val_dataset: validation dataset, instance of unet.dataset.ImageToImage2D (optional)\n            save_freq: frequency of saving the model and predictions from predict_dataset\n            save_model: bool indicating whether or not you wish to save the model itself\n                (useful for saving space)\n            predict_dataset: images to be predicted and saved during epochs determined\n                by save_freq, instance of unet.dataset.Image2D (optional)\n            n_batch: size of batch during training\n            metric_list: unet.utils.MetricList object, which contains metrics\n                to be recorded during validation\n            verbose: bool indicating whether or not print the logs to stdout\n\n        Returns:\n            logger: unet.utils.Logger object containing all logs recorded during\n                training\n        \"\"\"\n\n        logger = Logger(verbose=verbose)\n\n        # setting the current best loss to np.inf\n        min_loss = np.inf\n\n        # measuring the time elapsed\n        train_start = time()\n\n        for epoch_idx in range(1, n_epochs + 1):\n            # doing the epoch\n            train_logs = self.fit_epoch(dataset, n_batch=n_batch, shuffle=shuffle)\n\n            if self.scheduler is not None:\n                self.scheduler.step(train_logs['train_loss'])\n\n            if val_dataset is not None:\n                val_logs = self.val_epoch(val_dataset, n_batch=n_batch, metric_list=metric_list)\n                loss = val_logs['val_loss']\n            else:\n                loss = train_logs['train_loss']\n\n            if save_model:\n                # saving best model\n                if loss < min_loss:\n                    torch.save(self.net, os.path.join(self.checkpoint_folder, 'best_model.pt'))\n                    min_loss = val_logs['val_loss']\n\n                # saving latest model\n                torch.save(self.net, os.path.join(self.checkpoint_folder, 'latest_model.pt'))\n\n            # measuring time and memory\n            epoch_end = time()\n            # logging\n            logs = {'epoch': epoch_idx,\n                    'time': epoch_end - train_start,\n                    'memory': torch.cuda.memory_allocated(),\n                    **val_logs, **train_logs}\n            logger.log(logs)\n            logger.to_csv(os.path.join(self.checkpoint_folder, 'logs.csv'))\n\n            # saving model and logs\n            if save_freq and (epoch_idx % save_freq == 0):\n                epoch_save_path = os.path.join(self.checkpoint_folder, str(epoch_idx).zfill(4))\n                chk_mkdir(epoch_save_path)\n                torch.save(self.net, os.path.join(epoch_save_path, 'model.pt'))\n                if predict_dataset:\n                    self.predict_dataset(predict_dataset, epoch_save_path)\n\n        # save the logger\n        self.logger = logger\n\n        return logger\n\n    def predict_dataset(self, dataset, export_path):\n        \"\"\"\n        Predicts the images in the given dataset and saves it to disk.\n\n        Args:\n            dataset: the dataset of images to be exported, instance of unet.dataset.Image2D\n            export_path: path to folder where results to be saved\n        \"\"\"\n        self.net.train(False)\n        chk_mkdir(export_path)\n\n        for batch_idx, (X_batch, *rest) in enumerate(DataLoader(dataset, batch_size=1)):\n            if isinstance(rest[0][0], str):\n                image_filename = rest[0][0]\n            else:\n                image_filename = '%s.png' % str(batch_idx + 1).zfill(3)\n\n            X_batch = Variable(X_batch.to(device=self.device))\n            y_out = self.net(X_batch).cpu().data.numpy()\n\n            io.imsave(os.path.join(export_path, image_filename), y_out[0, 1, :, :])\n"
  },
  {
    "path": "unet/unet.py",
    "content": "import torch\r\nimport torch.nn as nn\r\nimport torch.nn.functional as F\r\n\r\nfrom .blocks import *\r\n\r\n\r\nclass UNet2D(nn.Module):\r\n    def __init__(self, in_channels, out_channels, conv_depths=(64, 128, 256, 512, 1024)):\r\n        assert len(conv_depths) > 2, 'conv_depths must have at least 3 members'\r\n\r\n        super(UNet2D, self).__init__()\r\n\r\n        # defining encoder layers\r\n        encoder_layers = []\r\n        encoder_layers.append(First2D(in_channels, conv_depths[0], conv_depths[0]))\r\n        encoder_layers.extend([Encoder2D(conv_depths[i], conv_depths[i + 1], conv_depths[i + 1])\r\n                               for i in range(len(conv_depths)-2)])\r\n\r\n        # defining decoder layers\r\n        decoder_layers = []\r\n        decoder_layers.extend([Decoder2D(2 * conv_depths[i + 1], 2 * conv_depths[i], 2 * conv_depths[i], conv_depths[i])\r\n                               for i in reversed(range(len(conv_depths)-2))])\r\n        decoder_layers.append(Last2D(conv_depths[1], conv_depths[0], out_channels))\r\n\r\n        # encoder, center and decoder layers\r\n        self.encoder_layers = nn.Sequential(*encoder_layers)\r\n        self.center = Center2D(conv_depths[-2], conv_depths[-1], conv_depths[-1], conv_depths[-2])\r\n        self.decoder_layers = nn.Sequential(*decoder_layers)\r\n\r\n    def forward(self, x, return_all=False):\r\n        x_enc = [x]\r\n        for enc_layer in self.encoder_layers:\r\n            x_enc.append(enc_layer(x_enc[-1]))\r\n\r\n        x_dec = [self.center(x_enc[-1])]\r\n        for dec_layer_idx, dec_layer in enumerate(self.decoder_layers):\r\n            x_opposite = x_enc[-1-dec_layer_idx]\r\n            x_cat = torch.cat(\r\n                [pad_to_shape(x_dec[-1], x_opposite.shape), x_opposite],\r\n                dim=1\r\n            )\r\n            x_dec.append(dec_layer(x_cat))\r\n\r\n        if not return_all:\r\n            return x_dec[-1]\r\n        else:\r\n            return x_enc + x_dec\r\n\r\n\r\nclass UNet3D(nn.Module):\r\n    def __init__(self, in_channels, out_channels, conv_depths=(64, 128, 256, 512, 1024)):\r\n        assert len(conv_depths) > 2, 'conv_depths must have at least 3 members'\r\n\r\n        super(UNet3D, self).__init__()\r\n\r\n        # defining encoder layers\r\n        encoder_layers = []\r\n        encoder_layers.append(First3D(in_channels, conv_depths[0], conv_depths[0]))\r\n        encoder_layers.extend([Encoder3D(conv_depths[i], conv_depths[i + 1], conv_depths[i + 1])\r\n                               for i in range(len(conv_depths)-2)])\r\n\r\n        # defining decoder layers\r\n        decoder_layers = []\r\n        decoder_layers.extend([Decoder3D(2 * conv_depths[i + 1], 2 * conv_depths[i], 2 * conv_depths[i], conv_depths[i])\r\n                               for i in reversed(range(len(conv_depths)-2))])\r\n        decoder_layers.append(Last3D(conv_depths[1], conv_depths[0], out_channels))\r\n\r\n        # encoder, center and decoder layers\r\n        self.encoder_layers = nn.Sequential(*encoder_layers)\r\n        self.center = Center3D(conv_depths[-2], conv_depths[-1], conv_depths[-1], conv_depths[-2])\r\n        self.decoder_layers = nn.Sequential(*decoder_layers)\r\n\r\n    def forward(self, x, return_all=False):\r\n        x_enc = [x]\r\n        for enc_layer in self.encoder_layers:\r\n            x_enc.append(enc_layer(x_enc[-1]))\r\n\r\n        x_dec = [self.center(x_enc[-1])]\r\n        for dec_layer_idx, dec_layer in enumerate(self.decoder_layers):\r\n            x_opposite = x_enc[-1-dec_layer_idx]\r\n            x_cat = torch.cat(\r\n                [pad_to_shape(x_dec[-1], x_opposite.shape), x_opposite],\r\n                dim=1\r\n            )\r\n            x_dec.append(dec_layer(x_cat))\r\n\r\n        if not return_all:\r\n            return x_dec[-1]\r\n        else:\r\n            return x_enc + x_dec\r\n\r\n\r\ndef pad_to_shape(this, shp):\r\n    \"\"\"\r\n    Pads this image with zeroes to shp.\r\n    Args:\r\n        this: image tensor to pad\r\n        shp: desired output shape\r\n\r\n    Returns:\r\n        Zero-padded tensor of shape shp.\r\n    \"\"\"\r\n    if len(shp) == 4:\r\n        pad = (0, shp[3] - this.shape[3], 0, shp[2] - this.shape[2])\r\n    elif len(shp) == 5:\r\n        pad = (0, shp[4] - this.shape[4], 0, shp[3] - this.shape[3], 0, shp[2] - this.shape[2])\r\n    return F.pad(this, pad)\r\n"
  },
  {
    "path": "unet/utils.py",
    "content": "import os\n\nimport pandas as pd\n\nfrom numbers import Number\nfrom typing import Container\nfrom collections import defaultdict\n\n\ndef chk_mkdir(*paths: Container) -> None:\n    \"\"\"\n    Creates folders if they do not exist.\n\n    Args:\n        paths: Container of paths to be created.\n    \"\"\"\n    for path in paths:\n        if not os.path.exists(path):\n            os.makedirs(path)\n\n\nclass Logger:\n    def __init__(self, verbose=False):\n        self.logs = defaultdict(list)\n        self.verbose = verbose\n\n    def log(self, logs):\n        for key, value in logs.items():\n            self.logs[key].append(value)\n\n        if self.verbose:\n            print(logs)\n\n    def get_logs(self):\n        return self.logs\n\n    def to_csv(self, path):\n        pd.DataFrame(self.logs).to_csv(path, index=None)\n\n\nclass MetricList:\n    def __init__(self, metrics):\n        assert isinstance(metrics, dict), '\\'metrics\\' must be a dictionary of callables'\n        self.metrics = metrics\n        self.results = {key: 0.0 for key in self.metrics.keys()}\n\n    def __call__(self, y_out, y_batch):\n        for key, value in self.metrics.items():\n            self.results[key] += value(y_out, y_batch)\n\n    def reset(self):\n        self.results = {key: 0.0 for key in self.metrics.keys()}\n\n    def get_results(self, normalize=False):\n        assert isinstance(normalize, bool) or isinstance(normalize, Number), '\\'normalize\\' must be boolean or a number'\n        if not normalize:\n            return self.results\n        else:\n            return {key: value/normalize for key, value in self.results.items()}\n"
  }
]