├── .gitattributes ├── LICENSE ├── README.md ├── ToyADMOS2_details.pdf ├── UsersManual.md ├── dcase2020_task2_baseline.patch ├── images ├── example_recipe.png ├── fig-recipe-ano-car.png ├── fig-recipe-ano-train.png ├── fig-recipe-nor-car.png ├── fig-recipe-nor-r0.png ├── fig-recipe-nor-r1.png ├── fig-recipe-nor-train.png ├── fig-recipe-settings.png ├── fig-recipe-sheets.png ├── fig-table1-defects.png └── toyadmos2-key-visual.png ├── mixer.py ├── recipe_benchmark.xlsx ├── recipe_example_car_shift.xlsx ├── recipe_template.xlsx ├── requirements.txt └── utils.py /.gitattributes: -------------------------------------------------------------------------------- 1 | *.patch binary 2 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | SOFTWARE LICENSE AGREEMENT FOR EVALUATION 2 | 3 | This SOFTWARE EVALUATION LICENSE AGREEMENT (this "Agreement") is a legal contract between a person who uses or otherwise accesses or installs the Software ("User(s)"), and Nippon Telegraph and Telephone corporation ("NTT"). 4 | READ THE TERMS AND CONDITIONS OF THIS AGREEMENT CAREFULLY BEFORE INSTALLING OR OTHERWISE ACCESSING OR USING NTT'S PROPRIETARY SOFTWARE ACCOMPANIED BY THIS AGREEMENT (the "SOFTWARE"). THE SOFTWARE IS COPYRIGHTED AND IT IS LICENSED TO USER UNDER THIS AGREEMENT, NOT SOLD TO USER. BY INSTALLING OR OTHERWISE ACCESSING OR USING THE SOFTWARE, USER ACKNOWLEDGES THAT USER HAS READ THIS AGREEMENT, THAT USER UNDERSTANDS IT, AND THAT USER ACCEPTS AND AGREES TO BE BOUND BY ITS TERMS. IF AT ANY TIME USER IS NOT WILLING TO BE BOUND BY THE TERMS OF THIS AGREEMENT, USER SHOULD TERMINATE THE INSTALLATION PROCESS, IMMEDIATELY CEASE AND REFRAIN FROM ACCESSING OR USING THE SOFTWARE AND DELETE ANY COPIES USER MAY HAVE. THIS AGREEMENT REPRESENTS THE ENTIRE AGREEMENT BETWEEN USER AND NTT CONCERNING THE SOFTWARE. 5 | 6 | 7 | BACKGROUND 8 | A. NTT is the owner of all rights, including all patent rights, copyrights and trade secret rights, in and to the Software and related documentation listed in Exhibit A to this Agreement. 9 | B. User wishes to obtain a royalty free license to use the Software to enable User to evaluate, and NTT wishes to grant such a license to User, pursuant and subject to the terms and conditions of this Agreement. 10 | C. As a condition to NTT's provision of the Software to User, NTT has required User to execute this Agreement. 11 | In consideration of these premises, and the mutual promises and conditions in this Agreement, the parties hereby agree as follows: 12 | 1. Grant of Evaluation License. NTT hereby grants to User, and User hereby accepts, under the terms and conditions of this Agreement, a royalty free, nontransferable and nonexclusive license to use the Software internally for the purposes of testing, analyzing, and evaluating the methods or mechanisms as shown in the research paper submitted by NTT to a certain academy. User may make a reasonable number of backup copies of the Software solely for User's internal use pursuant to the license granted in this Section 1. 13 | 2. Shipment and Installation. NTT will ship or deliver the Software by any method that NTT deems appropriate. User shall be solely responsible for proper installation of the Software. 14 | 3. Term. This Agreement is effective whichever is earlier (i) upon User's acceptance of the Agreement, or (ii) upon User's installing, accessing, and using the Software, even if User has not expressly accepted this Agreement. Without prejudice to any other rights, NTT may terminate this Agreement without notice to User (i) if User breaches or fails to comply with any of the limitations or other requirements described herein, and (ii) if NTT receives a notice from the academy stating that the research paper would not be published, and in any such case User agrees that NTT may, in addition to any other remedies it may have at law or in equity, remotely disable the Software. User may terminate this Agreement at any time by User's decision to terminate the Agreement to NTT and ceasing use of the Software. Upon any termination or expiration of this Agreement for any reason, User agrees to uninstall the Software and either return to NTT the Software and all copies thereof, or to destroy all such materials and provide written verification of such destruction to NTT. 15 | 4. Proprietary Rights 16 | (a) The Software is the valuable, confidential, and proprietary property of NTT, and NTT shall retain exclusive title to this property both during the term and after the termination of this Agreement. Without limitation, User acknowledges that all patent rights, copyrights and trade secret rights in the Software shall remain the exclusive property of NTT at all times. User shall use not less than reasonable care in safeguarding the confidentiality of the Software. 17 | (b) USER SHALL NOT, IN WHOLE OR IN PART, AT ANY TIME DURING THE TERM OF OR AFTER THE TERMINATION OF THIS AGREEMENT: (i) SELL, ASSIGN, LEASE, DISTRIBUTE, OR OTHERWISE TRANSFER THE SOFTWARE TO ANY THIRD PARTY; (ii) EXCEPT AS OTHERWISE PROVIDED HEREIN, COPY OR REPRODUCE THE SOFTWARE IN ANY MANNER; (iii) DISCLOSE THE SOFTWARE TO ANY THIRD PARTY, EXCEPT TO USER'S EMPLOYEES WHO REQUIRE ACCESS TO THE SOFTWARE FOR THE PURPOSES OF THIS AGREEMENT; (iv) MODIFY, DISASSEMBLE, DECOMPILE, REVERSE ENGINEER OR TRANSLATE THE SOFTWARE; OR (v) ALLOW ANY PERSON OR ENTITY TO COMMIT ANY OF THE ACTIONS DESCRIBED IN (i) THROUGH (iv) ABOVE. 18 | (c) User shall take appropriate action, by instruction, agreement, or otherwise, with respect to its employees permitted under this Agreement to have access to the Software to ensure that all of User's obligations under this Section 4 shall be satisfied. 19 | 5. Indemnity. User shall defend, indemnify and hold harmless NTT, its agents and employees, from any loss, damage, or liability arising in connection with User's improper or unauthorized use of the Software. NTT SHALL HAVE THE SOLE RIGHT TO CONDUCT DEFEND ANY ACTTION RELATING TO THE SOFTWARE. 20 | 6. Disclaimer. THE SOFTWARE IS LICENSED TO USER "AS IS," WITHOUT ANY TRAINING, MAINTENANCE, OR SERVICE OBLIGATIONS WHATSOEVER ON THE PART OF NTT. NTT MAKES NO EXPRESS OR IMPLIED WARRANTIES OF ANY TYPE WHATSOEVER, INCLUDING WITHOUT LIMITATION THE IMPLIED WARRANTIES OF MERCHANTABILITY, OF FITNESS FOR A PARTICULAR PURPOSE AND OF NON-INFRINGEMENT ON COPYRIGHT OR ANY OTHER RIGHT OF THIRD PARTIES. USER ASSUMES ALL RISKS ASSOCIATED WITH ITS USE OF THE SOFTWARE, INCLUDING WITHOUT LIMITATION RISKS RELATING TO QUALITY, PERFORMANCE, DATA LOSS, AND UTILITY IN A PRODUCTION ENVIRONMENT. 21 | 7. Limitation of Liability. IN NO EVENT SHALL NTT BE LIABLE TO USER OR TO ANY THIRD PARTY FOR ANY INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES, INCLUDING BUT NOT LIMITED TO DAMAGES FOR PERSONAL INJURY, PROPERTY DAMAGE, LOST PROFITS, OR OTHER ECONOMIC LOSS, ARISING IN CONNECTION WITH USER'S USE OF OR INABILITY TO USE THE SOFTWARE, IN CONNECTION WITH NTT'S PROVISION OF OR FAILURE TO PROVIDE SERVICES PERTAINING TO THE SOFTWARE, OR AS A RESULT OF ANY DEFECT IN THE SOFTWARE. THIS DISCLAIMER OF LIABILITY SHALL APPLY REGARD-LESS OF THE FORM OF ACTION THAT MAY BE BROUGHT AGAINST NTT, WHETHER IN CONTRACT OR TORT, INCLUDING WITHOUT LIMITATION ANY ACTION FOR NEGLIGENCE. USER'S SOLE REMEDY IN THE EVENT OF ANY BREACH OF THIS AGREEMENT BY NTT SHALL BE TERMINATION PURSUANT TO SECTION 3. 22 | 8. No Assignment or Sublicense. Neither this Agreement nor any right or license under this Agreement, nor the Software, may be sublicensed, assigned, or otherwise transferred by User without NTT's prior written consent. 23 | 9. General 24 | (a) If any provision, or part of a provision, of this Agreement is or becomes illegal, unenforceable, or invalidated, by operation of law or otherwise, that provision or part shall to that extent be deemed omitted, and the remainder of this Agreement shall remain in full force and effect. 25 | (b) This Agreement is the complete and exclusive statement of the agreement between the parties with respect to the subject matter hereof, and supersedes all written and oral contracts, proposals, and other communications between the parties relating to that subject matter. 26 | (c) Subject to Section 8, this Agreement shall be binding on, and shall inure to the benefit of, the respective successors and assigns of NTT and User. 27 | (d) If either party to this Agreement initiates a legal action or proceeding to enforce or interpret any part of this Agreement, the prevailing party in such action shall be entitled to recover, as an element of the costs of such action and not as damages, its attorneys' fees and other costs associated with such action or proceeding. 28 | (e) This Agreement shall be governed by and interpreted under the laws of Japan, without reference to conflicts of law principles. All disputes arising out of or in connection with this Agreement shall be finally settled by arbitration in Tokyo in accordance with the Commercial Arbitration Rules of the Japan Commercial Arbitration Association. The arbitration shall be conducted by three (3) arbitrators and in Japanese. The award rendered by the arbitrators shall be final and binding upon the parties. Judgment upon the award may be entered in any court having jurisdiction thereof. 29 | (f) NTT shall not be liable to the User or to any third party for any delay or failure to perform NTT's obligation set forth under this Agreement due to any cause beyond NTT's reasonable control. 30 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![toyadmos2 key visual](images/toyadmos2-key-visual.png) 2 | 3 | # ToyADMOS2: Another dataset of miniature-machine operating sounds for anomalous sound detection under domain shift conditions 4 | 5 | This repository provides a data mixer tool for ToyADMOS2 🚗 🚃, a large-scale dataset for anomaly detection in machine operating sounds (ADMOS) 6 | that consist of a large number of operating sounds of miniature machines (toys) under normal and anomaly conditions by deliberately damaging them. 7 | You can find the detail of the dataset on the [ToyADMOS2 dataset](https://zenodo.org/record/4580270#.YLniqmb7RzU) website. 8 | 9 | If you find the [ToyADMOS2](https://arxiv.org/abs/2106.02369) useful in your work, please consider citing [our paper](https://arxiv.org/abs/2106.02369). 10 | 11 | ```BibTeX 12 | @inproceedings{harada2021toyadmos2, 13 | author = "Harada, Noboru and Niizumi, Daisuke and Takeuchi, Daiki and Ohishi, Yasunori and Yasuda, Masahiro and Saito, Shoichiro", 14 | title = "{ToyADMOS2}: Another Dataset of Miniature-Machine Operating Sounds for Anomalous Sound Detection under Domain Shift Conditions", 15 | booktitle = "Proceedings of the 6th Detection and Classification of Acoustic Scenes and Events 2021 Workshop (DCASE2021)", 16 | address = "Barcelona, Spain", 17 | month = "November", 18 | year = "2021", 19 | pages = "1--5", 20 | isbn = "978-84-09-36072-7", 21 | doi. = "10.5281/zenodo.5770113", 22 | _pdf = {https://dcase.community/documents/workshop2021/proceedings/DCASE2021Workshop_Harada_6.pdf} 23 | } 24 | ``` 25 | 26 | ## What is the ToyADMOS2 dataset? 27 | 28 | ToyADMOS2 is a unique dataset, which we don't use as it is; it's a set of source material recording samples. 29 | We then use a tool provided in this repository, and generate new datasets according to our new `recipes`. 30 | 31 | The samples consist of the recordings under various conditions/configurations for normal/anomaly sounds. You can then edit/program your own set in the `recipe` so that you can compile new datasets for your research purposes. 32 | 33 | __A document for how we made it [ToyADMOS2_details.pdf](ToyADMOS2_details.pdf) is also available.__ 34 | You can also check the detail of anomaly conditions with photos. 35 | 36 | Please try making your own! 37 | 38 | ## Download dataset 39 | 40 | Visit the [ToyADMOS2 dataset](https://zenodo.org/record/4580270#.YLniqmb7RzU) website hosted by http://zenodo.org/, and download. 41 | 42 | ### Wanna See the Miniature Machines? 43 | 44 | Here're the videos of the toy car and the toy train: 45 | 46 | - 🚗 [Click to play: ToyCarRun.mp4](https://user-images.githubusercontent.com/14831220/118355869-672e7680-b5ad-11eb-947d-e90b1bfeb7ed.mp4) 47 | - 🚃 [Click to play: ToyTrainRun.mp4](https://user-images.githubusercontent.com/14831220/118355957-e0c66480-b5ad-11eb-950b-d874cfa0f0a8.mp4) 48 | 49 | ## Getting Started 50 | 51 | Install dependent packages according to the `requirements.txt`. 52 | 53 | This will install essential modules for running tools in this repository. 54 | 55 | ### Example: Making Example Dataset 56 | 57 | Run the following will create the equivalent benchmark dataset evaluated in the Table 3 of the paper, which is a compatible file-folder structure with the [DCASE2021 challenge task 2](http://dcase.community/challenge2021/task-unsupervised-detection-of-anomalous-sounds). This will create dataset folder `your_new_dataset`. **This will take about an hour**. 58 | 59 | ```sh 60 | # This creates `clean` dataset. 61 | python mixer.py /path/to/ToyADMOS2 your_new_dataset recipe_benchmark.xlsx clean 62 | # This creates SNR=6dB dataset. 63 | python mixer.py /path/to/ToyADMOS2 your_new_dataset recipe_benchmark.xlsx 6 64 | ``` 65 | 66 | - `recipe_example_car_shift.xlsx` is also another example. 67 | - `recipe_template` is a template, as well as one more example. 68 | 69 | ### Example: Running Baseline 70 | 71 | 1. Clone and apply a patch for making evaluation baseline based on [dcase2020_task2_baseline](https://github.com/y-kawagu/dcase2020_task2_baseline). 72 | 73 | ```sh 74 | git clone https://github.com/y-kawagu/dcase2020_task2_baseline 75 | cd dcase2020_task2_baseline && patch --binary < ../dcase2020_task2_baseline.patch 76 | ``` 77 | 78 | 2. Make a symbolic link for the baseline that finds data source at `dcase2020_task2_baseline/dev_data`. 79 | 80 | ```sh 81 | cd dcase2020_task2_baseline && ln -s ../your_new_dataset dev_data 82 | ``` 83 | 84 | 3. Run the baseline, then you can find the evaluation results stored in `result/result.csv`. 85 | 86 | ```sh 87 | cd dcase2020_task2_baseline 88 | python 00_train.py -d 89 | python 01_test.py -d 90 | ``` 91 | 92 | If you find anything missing when running `dcase2020_task2_baseline`, please follow the instruction in it to install basic modules. 93 | 94 | ## Making Your Dataset 95 | 96 | (Example of a recipe file, yes it's an Excel spreadsheet.) 97 | 98 | ![example recipe excel](images/example_recipe.png) 99 | 100 | You simply make a copy of template ([recipe_template.xlsx](recipe_template.xlsx)), edit yours, then run a tool. 101 | 102 | __You can find more information in the [UsersManual.md](UsersManual.md).__ 103 | 104 | ## License 105 | 106 | Please check the [LICENSE](LICENSE) for the detail. 107 | 108 | ## Acknowledgements 109 | 110 | The evaluation of this dataset use [y-kawagu/dcase2020_task2_baseline](https://github.com/y-kawagu/dcase2020_task2_baseline). We thank [@y-kawagu](https://github.com/y-kawagu) for your dedication to the DCASE challenges! 111 | 112 | This repository is an 2021 version, kudos to [@YumaKoizumi](https://github.com/YumaKoizumi/) for the 2020 efforts of the [ToyADMOS-dataset](https://github.com/YumaKoizumi/ToyADMOS-dataset). 113 | 114 | ## References 115 | 116 | - [Noboru Harada, Daisuke Niizumi, Daiki Takeuchi, Yasunori Ohishi, Masahiro Yasuda, and Shoichiro Saito, "ToyADMOS2: Another dataset of miniature-machine operating sounds for anomalous sound detection under domain shift conditions," 2021](https://arxiv.org/abs/2106.02369) 117 | - [Yuma Koizumi, Shoichiro Saito, Noboru Harada, Hisashi Uematsu and Keisuke Imoto, "ToyADMOS: A Dataset of Miniature-Machine Operating Sounds for Anomalous Sound Detection," WASPAA, 2019](https://arxiv.org/abs/1908.03299) 118 | - [Ryo Tanabe, Harsh Purohit, Kota Dohi, Takashi Endo, Yuki Nikaido, Toshiki Nakamura, and Yohei Kawaguchi, "MIMII DUE: Sound Dataset for Malfunctioning Industrial Machine Investigation and Inspection with Domain Shifts due to Changes in Operational and Environmental Conditions," 2021](https://arxiv.org/abs/2105.02702) 119 | - [DCASE 2020 Challenge Task 2 "Unsupervised Detection of Anomalous Sounds for Machine Condition Monitoring"](http://dcase.community/challenge2020/task-unsupervised-detection-of-anomalous-sounds) 120 | - Baseline system for DCASE 2020 Challenge Task 2 - [dcase2020_task2_baseline](https://github.com/y-kawagu/dcase2020_task2_baseline) 121 | - [DCASE 2021 Challenge Task 2 "Unsupervised Anomalous Sound Detection for Machine Condition Monitoring under Domain Shifted Conditions"](http://dcase.community/challenge2021/task-unsupervised-detection-of-anomalous-sounds) 122 | - Autoencoder-based baseline system for DCASE2021 Challenge Task 2 - [dcase2021_task2_baseline_ae](https://github.com/y-kawagu/dcase2021_task2_baseline_ae) 123 | - MobileNetV2-based baseline system for DCASE2021 Challenge Task 2 - [dcase2021_task2_mobile_net_v2](https://github.com/y-kawagu/dcase2021_task2_baseline_mobile_net_v2) 124 | -------------------------------------------------------------------------------- /ToyADMOS2_details.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nttcslab/ToyADMOS2-dataset/02a6f741a8c9d3a3df53798e818412d628cf02b2/ToyADMOS2_details.pdf -------------------------------------------------------------------------------- /UsersManual.md: -------------------------------------------------------------------------------- 1 | # ToyADMOS2 Dataset Users Manual 2 | 3 | For making new dataset with your own configurations, make your recipe file, then run a mixer tool (a python script). 4 | 5 | ## 1. Mixer tool (mixer.py) 6 | 7 | This tool mixes normal or anomaly samples with environmental noise samples at a specified SNR. 8 | 9 | ```sh 10 | Usage: mix_dataset.py SRC_FOLDER DEST_FOLDER RECIPE_FILE SNR_DB 11 | SRC_FOLDER Path to the ToyADMOS2 folder. 12 | DEST_FOLDER Path to a folder you want to create. 13 | RECIPE_FILE Recipe file for describing the use of samples. 14 | SNR_DB SNR in dB (-6, 6, or any integer), or `clean`. 15 | ``` 16 | 17 | One more option is written in recipe file: 18 | 19 | ![fig-recipe-settings.png](images/fig-recipe-settings.png) 20 | 21 | If True, all the _source domain_ normal samples will be shuffled after mixing all samples. 22 | This is for alleviating the data distribution problem that is seen when a dataset is composed of a small number of recordings 23 | and the difference of the data distributions between training and test set is too large. 24 | 25 | ### 1-1. Example of Runs of Mixer Tool 26 | 27 | This will create a clean dataset compatible with the paper benchmark, using source data files from a folder `ToyADMOS2`, 28 | and storing the resulting files under a folder `paper_clean`. This doesn't mix environmental noises. 29 | 30 | ```sh 31 | python mixer.py ToyADMOS2 paper_clean recipe_benchmark.xlsx clean 32 | ``` 33 | 34 | This will mix environmental noise at -6dB of the SNR. 35 | 36 | ```sh 37 | python mixer.py ToyADMOS2 paper_clean recipe_benchmark.xlsx -6 38 | ``` 39 | 40 | 41 | ## 2. Making Recipe 42 | 43 | Which samples, for how much, with which noise, for what mics? 44 | The recipe is for configuring these details, as an Excel spreadsheet. 45 | 46 | ### 2-1. Overview of the Recipe File 47 | 48 | - Two machines: ToyCar and ToyTrain. 49 | - Two types of recordings: Normal sounds and anomalous sounds. 50 | - Normal sounds are for training and test set, whereas anomalous sounds are for test set only. 51 | - Normal sounds have 150 recording patterns, whereas anomalous sounds have 300 patterns. 52 | - A recipe file has five or more sheets: `Settings & Notes`, `NormalToyCar`, `AnomalyToyCar`, `NormalToyTrain`, and `AnomalyToyTrain`. 53 | - `Settings & Notes` is for writing settings and descriptions/notes of the recipe. 54 | - `NormalToyCar`, `AnomalyToyCar` are for configuring for toy car recordings. 55 | - `NormalToyTrain`, `AnomalyToyTrain` are for toy train recordings. 56 | 57 | (You can find sheet tabs at the bottom of Excel app.) 58 | 59 | ![fig-recipe-sheets.png](images/fig-recipe-sheets.png) 60 | 61 | ### 2-2. `NormalToyCar` 62 | 63 | In the `NormalXXX` sheets, we have 150 patterns each. 64 | 65 | ![fig-recipe-nor-car](images/fig-recipe-nor-car.png) 66 | 67 | The information on the recording patterns is written in columns A-I. 68 | 69 | - `No.` -- Recording pattern no. 70 | - `Folder` -- Relative path in the ToyADMOS2 dataset. 71 | - `FileID` -- Recording pattern ID. 72 | - `Model` -- The `machine configuration` (A to E). 73 | - `CarID` -- ID of machine hardware recorded (used in the recording work). 74 | - `Speed` -- The `operating speed` (1 to 5), corresponding to the five voltage levels, 2.8, 3.1, 3.4, 3.7, and 4.0 V. 75 | - `Defect` -- Not used for normal samples. 76 | - `D. Level` -- The `damage level`, not used here. 77 | - `# of Rec.` -- Number of recording samples per mic. i.e. There are actually 260 x 5 = 1,300 samples if this is 260. 78 | 79 | You will write configuration requirements (`r# = requirement`) in columns named `r#_xxx` as follows: 80 | 81 | ![fig-recipe-nor-r0](images/fig-recipe-nor-r0.png) 82 | 83 | - `r0_pat` -- Resulting pathname pattern. `ToyCar/train/section_00_source_train_normal_?????.wav` will create files starting from `section_00_source_train_normal_00001.wav` under a folder `ToyCar/train`. 84 | - `r0_mics` -- ID of mics to be used. `[3]` uses samples from mic `3` only, or `[4, 5]` uses samples from two mics `4` and `5`. 85 | - `r0_nz` -- ID of environmental noise to be mixed. A single number is accepted: 1 to 4. 86 | - `r0_qty` -- Number of samples to create. 87 | 88 | If you need two or more requirements with one recording, here you can write in `r1_xxx`, `r2_xxx`, or more requirement columns. 89 | 90 | ![fig-recipe-nor-r1](images/fig-recipe-nor-r1.png) 91 | 92 | - `r1_pat` -- The same as `r0_pat`. `ToyCar/source_test/section_00_source_test_normal_?????.wav` will create files starting from `section_00_source_test_normal_00001.wav` under a folder `ToyCar/source_test`. 93 | - `r1_mics` -- The same as `r0_mics`. 94 | - `r1_nz` -- The same as `r0_nz`. 95 | - `r1_qty` -- The same as `r0_qty`. Wait, what's the `R25`? `R` means _replacement_. So `R25` will always pick samples from the beginning. If you have `25` in `r0_qty` and `10` in `r1_qty`, the first 25 samples will be used for making files with the pattern `r0_xxx`, and the next 10 samples are for the patten `r1_xxx`. 96 | 97 | ### 2-3. `AnomalyToyCar` 98 | 99 | In the `AnomalyXXX` sheets, we have 300 patterns each. 100 | 101 | ![fig-recipe-ano-car](images/fig-recipe-ano-car.png) 102 | 103 | - `Defect` -- The `anomaly conditions` defined in the Table 1 on the paper. 104 | - `D. Level` -- The `damage level`: High, mid, or low. 105 | - `# of Rec.` -- The same as `NormalToyCar`, and all the recordings has 27 samples per mic. i.e. There are actually 27 x 5 = 135 samples if we use samples from all mics. 106 | 107 | ![fig-table1-defects.png](images/fig-table1-defects.png) 108 | 109 | The usage of the requirements are the same with `NormalXXX` sheets. 110 | 111 | ### 2-4. `NormalToyTrain` 112 | 113 | ![fig-recipe-nor-train](images/fig-recipe-nor-train.png) 114 | 115 | The number of mic is 8 for toy train. 116 | 117 | - `# of Rec.` -- There are actually 65 x 8 = 260 samples if this is 65. 118 | 119 | The toy train normal recordings have about 1/4 number of samples compared to toy car. 120 | 121 | It is designed to use the samples from four mics as one set; mic `[1,2,3,4]` is the first set, and `[5,6,7,8]` is the second. 122 | 123 | - `r#_mics` -- ID of mics: `[1,2,3,4]`, `[5,6,7,8]`, or any combination for your purpose. `[1,2,3,4,5,6,7,8]` will use all the mics. 124 | 125 | ### 2-5. `AnomalyToyTrain` 126 | 127 | ![fig-recipe-ano-train](images/fig-recipe-ano-train.png) 128 | 129 | The same design with the `NormalToyTrain`. 130 | 131 | - `# of Rec.` -- All the recordings has 7 samples per mic. i.e. There are actually 7 x 8 = 56 samples if we use samples from all mics. 132 | 133 | ### 2-6. Other Notes 134 | 135 | - The normal or anomaly samples are used from the beginning of sorted order of list of files, whereas the environmental noise samples are randomly picked with replacement. 136 | - Logs are created under the destination folder, `log-NormalToyCar.txt` for example. 137 | 138 | (Example log) 139 | 140 | ``` 141 | 2021-05-01 12:03 INFO CN031-carB1-speed1_mic3_00001.wav|0.0406 + N1_mic3_01595.wav|0.0657 -> section_00_source_train_normal_00001_B1s1.wav snr=0dB 142 | 2021-05-01 12:03 INFO CN031-carB1-speed1_mic3_00002.wav|0.0406 + N1_mic3_03383.wav|0.0630 -> section_00_source_train_normal_00002_B1s1.wav snr=0dB 143 | 2021-05-01 12:03 INFO CN031-carB1-speed1_mic3_00003.wav|0.0406 + N1_mic3_03129.wav|0.0638 -> section_00_source_train_normal_00003_B1s1.wav snr=0dB 144 | 2021-05-01 12:03 INFO CN031-carB1-speed1_mic3_00004.wav|0.0406 + N1_mic3_03383.wav|0.0630 -> section_00_source_train_normal_00004_B1s1.wav snr=0dB 145 | 2021-05-01 12:03 INFO CN031-carB1-speed1_mic3_00005.wav|0.0406 + N1_mic3_04219.wav|0.0744 -> section_00_source_train_normal_00005_B1s1.wav snr=0dB 146 | 2021-05-01 12:04 INFO CN031-carB1-speed1_mic3_00006.wav|0.0406 + N1_mic3_01770.wav|0.0197 -> section_00_source_train_normal_00006_B1s1.wav snr=0dB 147 | ``` 148 | 149 | This shows that normal samples used are starting from `CN031-carB1-speed1_mic3_00001.wav`, mixed with randomly picked noise like `N1_mic3_01595.wav`, and output to the `section_00_source_train_normal_00001_B1s1.wav` at 0dB of the SNR. 150 | 151 | The numbers following filenames (`0.0406` for example) are the RMS amplitude; 152 | - RMS amplitude of normal or anomolous sample is the average value among samples, 153 | - whereas that of noise sample is calculated from the file. 154 | 155 | -------------------------------------------------------------------------------- /dcase2020_task2_baseline.patch: -------------------------------------------------------------------------------- 1 | --- dcase2020_task2_baseline/01_test.py 2021-05-15 12:52:26.213931094 +0900 2 | +++ /lab/task2/dcase2020_task2_baseline/01_test.py 2021-05-01 17:29:36.726326799 +0900 3 | @@ -63,11 +63,11 @@ def get_machine_id_list_for_test(target_ 4 | list of machine IDs extracted from the names of test files 5 | """ 6 | # create test files 7 | - dir_path = os.path.abspath("{dir}/{dir_name}/*.{ext}".format(dir=target_dir, dir_name=dir_name, ext=ext)) 8 | + dir_path = os.path.abspath("{dir}/*{dir_name}/*.{ext}".format(dir=target_dir, dir_name=dir_name, ext=ext)) 9 | file_paths = sorted(glob.glob(dir_path)) 10 | # extract id 11 | machine_id_list = sorted(list(set(itertools.chain.from_iterable( 12 | - [re.findall('id_[0-9][0-9]', ext_id) for ext_id in file_paths])))) 13 | + [re.findall('section_[0-9][0-9]', ext_id) for ext_id in file_paths])))) 14 | return machine_id_list 15 | 16 | 17 | @@ -107,14 +107,14 @@ def test_file_list_generator(target_dir, 18 | # development 19 | if mode: 20 | normal_files = sorted( 21 | - glob.glob("{dir}/{dir_name}/{prefix_normal}_{id_name}*.{ext}".format(dir=target_dir, 22 | + glob.glob("{dir}/*{dir_name}/{id_name}*_{prefix_normal}_*.{ext}".format(dir=target_dir, 23 | dir_name=dir_name, 24 | prefix_normal=prefix_normal, 25 | id_name=id_name, 26 | ext=ext))) 27 | normal_labels = numpy.zeros(len(normal_files)) 28 | anomaly_files = sorted( 29 | - glob.glob("{dir}/{dir_name}/{prefix_anomaly}_{id_name}*.{ext}".format(dir=target_dir, 30 | + glob.glob("{dir}/*{dir_name}/{id_name}*_{prefix_anomaly}_*.{ext}".format(dir=target_dir, 31 | dir_name=dir_name, 32 | prefix_anomaly=prefix_anomaly, 33 | id_name=id_name, 34 | --- dcase2020_task2_baseline/common.py 2021-05-15 12:52:26.213931094 +0900 35 | +++ /lab/task2/dcase2020_task2_baseline/common.py 2021-05-01 17:39:42.250290331 +0900 36 | @@ -184,6 +184,7 @@ def select_dirs(param, mode): 37 | logger.info("load_directory <- evaluation") 38 | dir_path = os.path.abspath("{base}/*".format(base=param["eval_directory"])) 39 | dirs = sorted(glob.glob(dir_path)) 40 | + dirs = [f for f in dirs if os.path.isdir(f)] 41 | return dirs 42 | 43 | ######################################################################## 44 | -------------------------------------------------------------------------------- /images/example_recipe.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nttcslab/ToyADMOS2-dataset/02a6f741a8c9d3a3df53798e818412d628cf02b2/images/example_recipe.png -------------------------------------------------------------------------------- /images/fig-recipe-ano-car.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nttcslab/ToyADMOS2-dataset/02a6f741a8c9d3a3df53798e818412d628cf02b2/images/fig-recipe-ano-car.png -------------------------------------------------------------------------------- /images/fig-recipe-ano-train.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nttcslab/ToyADMOS2-dataset/02a6f741a8c9d3a3df53798e818412d628cf02b2/images/fig-recipe-ano-train.png -------------------------------------------------------------------------------- /images/fig-recipe-nor-car.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nttcslab/ToyADMOS2-dataset/02a6f741a8c9d3a3df53798e818412d628cf02b2/images/fig-recipe-nor-car.png -------------------------------------------------------------------------------- /images/fig-recipe-nor-r0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nttcslab/ToyADMOS2-dataset/02a6f741a8c9d3a3df53798e818412d628cf02b2/images/fig-recipe-nor-r0.png -------------------------------------------------------------------------------- /images/fig-recipe-nor-r1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nttcslab/ToyADMOS2-dataset/02a6f741a8c9d3a3df53798e818412d628cf02b2/images/fig-recipe-nor-r1.png -------------------------------------------------------------------------------- /images/fig-recipe-nor-train.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nttcslab/ToyADMOS2-dataset/02a6f741a8c9d3a3df53798e818412d628cf02b2/images/fig-recipe-nor-train.png -------------------------------------------------------------------------------- /images/fig-recipe-settings.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nttcslab/ToyADMOS2-dataset/02a6f741a8c9d3a3df53798e818412d628cf02b2/images/fig-recipe-settings.png -------------------------------------------------------------------------------- /images/fig-recipe-sheets.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nttcslab/ToyADMOS2-dataset/02a6f741a8c9d3a3df53798e818412d628cf02b2/images/fig-recipe-sheets.png -------------------------------------------------------------------------------- /images/fig-table1-defects.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nttcslab/ToyADMOS2-dataset/02a6f741a8c9d3a3df53798e818412d628cf02b2/images/fig-table1-defects.png -------------------------------------------------------------------------------- /images/toyadmos2-key-visual.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nttcslab/ToyADMOS2-dataset/02a6f741a8c9d3a3df53798e818412d628cf02b2/images/toyadmos2-key-visual.png -------------------------------------------------------------------------------- /mixer.py: -------------------------------------------------------------------------------- 1 | """ToyADMOS2: Data mixer tool. 2 | 3 | This tool mixes ToyADMOS2 data samples according to your recipe, and creates the final dataset for you. 4 | 5 | Usage: mix_dataset.py SRC_FOLDER DEST_FOLDER RECIPE_FILE SNR_DB 6 | SRC_FOLDER Path to the ToyADMOS2 folder. 7 | DEST_FOLDER Path to a folder you want to create. 8 | RECIPE_FILE Recipe file for describing the use of samples. 9 | SNR_DB SNR in dB (-6, 6, or any integer), or `clean`. 10 | 11 | One more option is written in the recipe file: 12 | "Settings & Notes" sheet 13 | "Shuffle Normal" column 14 | True or False 15 | If True, all the _source_ normal samples will be shuffled after mixing all samples. 16 | """ 17 | 18 | import pandas as pd 19 | import numpy as np 20 | from pathlib import Path 21 | from tqdm import tqdm 22 | import shutil 23 | import fire 24 | import librosa 25 | from librosa.core.audio import __audioread_load 26 | import soundfile as sf 27 | import warnings 28 | warnings.simplefilter('ignore') 29 | 30 | from utils import get_logger, count_num_of_consecutive_letter 31 | 32 | 33 | ### OPTION: Sampling rate of the resulting samples. You can also configure this. 34 | FINAL_SR = 16000 35 | 36 | # Global variables. 37 | MAX_DATA_REQUEST = 10 38 | MAX_MICS = 8 39 | logger = None 40 | 41 | 42 | def process_one(src_pair, src_dirs, rms_pair, dest_file, snr_db): 43 | """Processes a sample; mix recorded audio with noise audio.""" 44 | 45 | src_signal, src_noise, _ = src_pair # signal sample file, noise sample file, mic# 46 | sig_dir, noise_dir = src_dirs 47 | sig_rms, noise_rms = rms_pair 48 | 49 | # sig, sr_sig = librosa.load(sig_dir/src_signal, sr=None) 50 | sig, sr_sig = __audioread_load(sig_dir/src_signal, offset=0.0, duration=None, dtype=np.float32) 51 | sig = librosa.to_mono(sig) # for making sure 52 | sig = librosa.resample(sig, orig_sr=sr_sig, target_sr=FINAL_SR) 53 | 54 | if snr_db == 'clean': 55 | # Clean 56 | logger.info(f'{src_signal}|{sig_rms:.4f} (clean) -> {dest_file.name}') 57 | mixed = sig 58 | else: 59 | # Mix sounds 60 | logger.info(f'{src_signal}|{sig_rms:.4f} + {src_noise}|{noise_rms:.4f} -> {dest_file.name} snr={snr_db}dB') 61 | 62 | # noi, sr_noi = librosa.load(noise_dir/src_noise, sr=None) 63 | noi, sr_noi = __audioread_load(noise_dir/src_noise, offset=0.0, duration=None, dtype=np.float32) 64 | noi = librosa.to_mono(noi) # for making sure 65 | noi = librosa.resample(noi, orig_sr=sr_noi, target_sr=FINAL_SR) 66 | 67 | k = sig_rms / noise_rms / 10**(snr_db / 20.) 68 | mixed = (sig * (1. / (1. + k))) + (noi * (k / (1. + k))) 69 | 70 | finalized = (mixed * 32767.0).astype(np.int16) 71 | 72 | sf.write(dest_file, finalized, FINAL_SR, 'PCM_16') 73 | 74 | 75 | def process_data_requests(src_df, src_root, dest_root, snr_db, df, rows): 76 | """Processes one request in the recipe.""" 77 | 78 | # Make a list of tuple of noise file and its rms. 79 | rms_car = src_df[src_df.index.str.match('^C.\d+')].rms.mean() 80 | rms_train = src_df[src_df.index.str.match('^T.\d+')].rms.mean() 81 | 82 | # Process requests one by one 83 | rows = ['Folder', 'FileID'] + rows 84 | count = 0 85 | for src_dir, src_id, ptn, mics, noise, qty in df[rows].values: 86 | if ptn == '' or qty == '': continue 87 | mics = eval(mics) # '[1,2,3]' -> list([1,2,3]) 88 | replaceable = ('R' == str(qty)[0]) # replaceable or not 89 | qty = int(qty[1:]) if 'R' == str(qty)[0] else int(qty) 90 | noise = int(noise) 91 | machine = str(src_dir).split('/')[0] 92 | 93 | # base signal RMS amplitude 94 | rms = rms_car if src_id[0] == 'C' else rms_train 95 | 96 | # Folders 97 | src_dir = Path(src_root)/src_dir 98 | dest_dir = (Path(dest_root)/ptn).parent 99 | noise_dir = Path(src_root)/f'{machine}/env_noise' 100 | 101 | # Find src machine sounds for each mic 102 | if replaceable: 103 | logger.info(f'**WARNING** SAMPLE REPLACEABLE {src_id}') 104 | srcs = {k: [v for v in src_df.index.values if f'{src_id}_mic{k}' in v] for k in mics} 105 | else: 106 | srcs = {k: [v for v in src_df.index.values if f'{src_id}_mic{k}' in v and not src_df.loc[v, "used"]] for k in mics} 107 | srcs = {k: v for k, v in srcs.items() if len(v) > 0} 108 | sizes = {k: len(v) for k, v in srcs.items()} 109 | logger.info(f'Processing {rows[2]}/{qty} {src_dir} {src_id} with samples: {sizes} => {dest_dir}') 110 | 111 | # Find noise files for each mic 112 | noises = {k: [v for v in src_df.index.values if f'{machine}_N{noise}_mic{k}' in v] for k in mics} 113 | noises = {k: [v[len(machine) + 1:] for v in noises[k]] for k in mics} # fix noise filenames 114 | nz_sizes = {k: len(v) for k, v in noises.items()} 115 | logger.info(f' with noise samples: {nz_sizes}') 116 | 117 | # Check the number of available machine samples 118 | total_available = sum(sizes.values()) 119 | # YOUR OPTION: Uncomment if you just skip requests that cannot be satisfied. 120 | # if total_available < qty: ############################ 121 | # logger.warning(f'!!!!!!! SKIPPING total_available < qty ({total_available} < {qty})') 122 | # qty = total_available ############################ 123 | assert total_available >= qty, f'Samples not enough for satisfy your request, # of samples {total_available} < {qty} for pattern: {ptn}' 124 | 125 | # Check the number of available noise samples 126 | min_num_of_noise = min(list(nz_sizes.values())) 127 | assert min_num_of_noise > 0, f'(For some of mic) No noise available for satisfy your request, please check your request "qx_nz".' 128 | 129 | # Make a sequential list of mp4 files to use files from each mic equally 130 | selected_pairs = [] 131 | local_count = 0 132 | while local_count < qty: 133 | for k in srcs.keys(): 134 | if len(srcs[k]) == 0: 135 | logger.debug(f'SHORT OF: {src_id} mic {k}') 136 | continue 137 | sig = srcs[k].pop(0) 138 | src_df.loc[sig, 'used'] = True 139 | selected_pairs.append([sig, np.random.choice(noises[k]), k]) 140 | local_count += 1 141 | 142 | # Create a destination folder 143 | dest_dir.mkdir(parents=True, exist_ok=True) 144 | 145 | # Iterate for the quantity 146 | file_ptn = Path(ptn).name 147 | num_num = count_num_of_consecutive_letter(file_ptn, '?').max() 148 | assert num_num > 0, f'*** Please set ? in your patterns pattern: {ptn}' 149 | numstr = '?' * num_num 150 | num_pos = file_ptn.find(numstr) 151 | num_search_ptn = file_ptn[:num_pos+num_num] + '*.wav' 152 | for i in range(qty): 153 | # Find file number in dest_dir 154 | existings = sorted(dest_dir.glob(num_search_ptn)) 155 | cur_max = 0 if len(existings) == 0 else int(existings[-1].name[num_pos:num_pos+num_num]) 156 | # Process one file 157 | cur_filename = file_ptn.replace(numstr, f'%.0{num_num}d' % (cur_max + 1)) 158 | process_one(selected_pairs[i], [src_dir, noise_dir], [rms, src_df.loc[f'{machine}_{selected_pairs[i][1]}', 'rms']], dest_dir/cur_filename, snr_db) 159 | count += 1 160 | return count 161 | 162 | 163 | def do_shuffle_normal(dest_folder): 164 | """Shuffles normal samples.""" 165 | 166 | subdirs = [d for d in Path(dest_folder).glob('*') if d.is_dir()] 167 | for subdir in subdirs: 168 | tmp_file = subdir/'temp.wav' 169 | files = sorted(subdir.glob(f'*/section_*_source_*_normal*.wav')) 170 | print(f'{subdir} for {len(files)} files, temporary file={tmp_file}') 171 | print([str(f) for f in files[:3]]) 172 | src = files.copy() 173 | dest = files.copy() 174 | np.random.shuffle(src) 175 | for i in range(len(files)): 176 | a, b = src[i], dest[i] 177 | if str(a) == str(b): 178 | print('skip', i) 179 | continue 180 | print(i, end=' ') 181 | #print(src[i], '<->', dest[i]) 182 | shutil.move(a, tmp_file) 183 | shutil.move(b, a) 184 | shutil.move(tmp_file, b) 185 | print() 186 | 187 | 188 | def process_recipe_file(src_folder, dest_folder, recipe_file, snr_db): 189 | """Processes a recipe, main program.""" 190 | 191 | global logger 192 | # Load ToyADMOS2/stat.csv 193 | try: 194 | src_df = pd.read_csv(src_folder + '/stat.csv').set_index('filename') 195 | src_df['used'] = False 196 | except Exception as e: 197 | print(f'Cannot read {src_folder + "/stat.csv"}.') 198 | print(e) 199 | exit(-1) 200 | # Load recipe file sheets 201 | try: 202 | dfs = pd.read_excel(recipe_file, sheet_name=None, engine='openpyxl') 203 | except Exception as e: 204 | print(f'Cannot read {recipe_file}.') 205 | print(e) 206 | exit(-1) 207 | # Read settings 208 | shuffle_normal = False 209 | try: 210 | shuffle_normal = dfs['Settings & Notes']['Shuffle Normal'].values[0] 211 | assert shuffle_normal == True or shuffle_normal == False 212 | except: 213 | print('WARNING: Cannot read "Shuffle Normal" column in the sheet "Settings & Notes".') 214 | print('** Shuffles normal samples at the end of the process **' if shuffle_normal else '(No shuffle normal)') 215 | 216 | Path(dest_folder).mkdir(parents=True, exist_ok=True) 217 | count = 0 218 | logger = get_logger('mixer', to_file=Path(dest_folder)/f'log.txt', always_renew=True) 219 | logger.info(f'From {src_folder} to {dest_folder} according to {recipe_file}, snr={snr_db}dB') 220 | for target, df in dfs.items(): 221 | if target[0] == '_' or target == 'Settings & Notes': 222 | print(f'skipping {target}...') 223 | continue 224 | 225 | logger.info(f'On {target}:') 226 | 227 | df = df[~pd.isna(df['No.'])].fillna('') 228 | for req in tqdm(range(MAX_DATA_REQUEST)): 229 | if f'r{req}_pat' not in df.columns: 230 | continue 231 | 232 | r_pattern = f'r{req}_pat' 233 | r_mics = f'r{req}_mics' 234 | r_noise = f'r{req}_nz' 235 | r_qty = f'r{req}_qty' 236 | 237 | count += process_data_requests(src_df, src_folder, dest_folder, snr_db, df, [r_pattern, r_mics, r_noise, r_qty]) 238 | 239 | if shuffle_normal: 240 | logger.info('Shuffle normal samples...') 241 | do_shuffle_normal(dest_folder) 242 | 243 | logger.info(f'Processed {count} files.') 244 | return count 245 | 246 | 247 | # main 248 | fire.Fire(process_recipe_file) 249 | -------------------------------------------------------------------------------- /recipe_benchmark.xlsx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nttcslab/ToyADMOS2-dataset/02a6f741a8c9d3a3df53798e818412d628cf02b2/recipe_benchmark.xlsx -------------------------------------------------------------------------------- /recipe_example_car_shift.xlsx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nttcslab/ToyADMOS2-dataset/02a6f741a8c9d3a3df53798e818412d628cf02b2/recipe_example_car_shift.xlsx -------------------------------------------------------------------------------- /recipe_template.xlsx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nttcslab/ToyADMOS2-dataset/02a6f741a8c9d3a3df53798e818412d628cf02b2/recipe_template.xlsx -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | numpy 2 | pandas 3 | openpyxl 4 | fire 5 | librosa 6 | soundfile 7 | tqdm 8 | -------------------------------------------------------------------------------- /utils.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | from pathlib import Path 3 | import logging 4 | from itertools import groupby 5 | 6 | 7 | def get_logger(name, to_file=None, always_renew=False): 8 | logging.basicConfig(format='%(asctime)s %(levelname)s %(message)s', 9 | datefmt='%Y-%m-%d %H:%M', level=logging.INFO) 10 | logger = logging.getLogger(name) 11 | 12 | if to_file is not None: 13 | if always_renew and Path(to_file).exists(): 14 | Path(to_file).unlink() 15 | formatter = logging.Formatter('%(asctime)s %(levelname)s %(message)s', 16 | datefmt='%Y-%m-%d %H:%M') 17 | output_file_handler = logging.FileHandler(to_file) 18 | output_file_handler.setFormatter(formatter) 19 | logger.addHandler(output_file_handler) 20 | 21 | return logger 22 | 23 | 24 | def count_num_of_consecutive_letter(text, target): 25 | """Count number of `target` letter in text. 26 | Example: 27 | >>> count_num_of_consecutive_letter('ToyCar/source_test/abcde_????.wav', '?') 28 | array([4]) 29 | """ 30 | groups = groupby(text) 31 | result = [sum(1 for _ in group) for label, group in groups if label == target] 32 | return np.array(result) 33 | 34 | 35 | def calc_rms_voladj(wave): 36 | rms = np.sqrt(np.mean((wave/32767)**2)) 37 | vol_adj = 32767/np.clip(1, 32767, np.abs(wave).max()) 38 | return rms, vol_adj 39 | 40 | 41 | if __name__ == '__main__': 42 | logger = get_logger(__name__, to_file='/tmp/test_log.txt') 43 | logger.info('Info abc') 44 | logger.debug('Debug abc') 45 | --------------------------------------------------------------------------------