From 060d7e85df7d2b3222287b3eacef9203c86a059e Mon Sep 17 00:00:00 2001 From: "zach@fastcyberlink.com" Date: Fri, 31 May 2024 22:05:16 +0000 Subject: [PATCH 01/10] initial bundle commit 05.31.24 --- .../LICENSE | 201 +++++++++ .../configs/TS_test.json | 406 ++++++++++++++++++ .../configs/evaluate-standalone-parallel.yaml | 18 + .../configs/evaluate-standalone.yaml | 17 + .../configs/evaluate.yaml | 63 +++ .../configs/inference.yaml | 161 +++++++ .../configs/inference_segresnet.yaml | 140 ++++++ .../configs/inference_swinunetr.yaml | 137 ++++++ .../configs/inference_trt.yaml | 8 + .../configs/logging.conf | 21 + .../configs/metadata.json | 101 +++++ .../configs/train-multigpu.yaml | 372 ++++++++++++++++ .../configs/train.yaml | 361 ++++++++++++++++ .../docs/README.md | 145 +++++++ .../docs/data_license.txt | 35 ++ .../large_files.yaml | 7 + .../scripts/__init__.py | 1 + .../scripts/compute_metric.py | 162 +++++++ .../scripts/lr_scheduler.py | 211 +++++++++ .../scripts/monai_utils.py | 231 ++++++++++ .../scripts/prepare_datalist_monailabel.py | 177 ++++++++ .../scripts/utils.py | 2 + 22 files changed, 2977 insertions(+) create mode 100644 models/pediatric_abdominal_ct_segmentation/LICENSE create mode 100644 models/pediatric_abdominal_ct_segmentation/configs/TS_test.json create mode 100644 models/pediatric_abdominal_ct_segmentation/configs/evaluate-standalone-parallel.yaml create mode 100644 models/pediatric_abdominal_ct_segmentation/configs/evaluate-standalone.yaml create mode 100644 models/pediatric_abdominal_ct_segmentation/configs/evaluate.yaml create mode 100644 models/pediatric_abdominal_ct_segmentation/configs/inference.yaml create mode 100644 models/pediatric_abdominal_ct_segmentation/configs/inference_segresnet.yaml create mode 100644 models/pediatric_abdominal_ct_segmentation/configs/inference_swinunetr.yaml create mode 100644 models/pediatric_abdominal_ct_segmentation/configs/inference_trt.yaml create mode 100644 models/pediatric_abdominal_ct_segmentation/configs/logging.conf create mode 100644 models/pediatric_abdominal_ct_segmentation/configs/metadata.json create mode 100644 models/pediatric_abdominal_ct_segmentation/configs/train-multigpu.yaml create mode 100644 models/pediatric_abdominal_ct_segmentation/configs/train.yaml create mode 100644 models/pediatric_abdominal_ct_segmentation/docs/README.md create mode 100644 models/pediatric_abdominal_ct_segmentation/docs/data_license.txt create mode 100644 models/pediatric_abdominal_ct_segmentation/large_files.yaml create mode 100644 models/pediatric_abdominal_ct_segmentation/scripts/__init__.py create mode 100644 models/pediatric_abdominal_ct_segmentation/scripts/compute_metric.py create mode 100644 models/pediatric_abdominal_ct_segmentation/scripts/lr_scheduler.py create mode 100644 models/pediatric_abdominal_ct_segmentation/scripts/monai_utils.py create mode 100644 models/pediatric_abdominal_ct_segmentation/scripts/prepare_datalist_monailabel.py create mode 100644 models/pediatric_abdominal_ct_segmentation/scripts/utils.py diff --git a/models/pediatric_abdominal_ct_segmentation/LICENSE b/models/pediatric_abdominal_ct_segmentation/LICENSE new file mode 100644 index 00000000..261eeb9e --- /dev/null +++ b/models/pediatric_abdominal_ct_segmentation/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/models/pediatric_abdominal_ct_segmentation/configs/TS_test.json b/models/pediatric_abdominal_ct_segmentation/configs/TS_test.json new file mode 100644 index 00000000..f8f5d807 --- /dev/null +++ b/models/pediatric_abdominal_ct_segmentation/configs/TS_test.json @@ -0,0 +1,406 @@ +{ + "training": [ + { + "image": "/processed/Public/CT_TotalSegmentator/s0013.nii.gz", + "label": "/processed/Public/CT_TotalSegmentator/TS_split/test/s0013.nii.gz" + }, + { + "image": "/processed/Public/CT_TotalSegmentator/s0029.nii.gz", + "label": "/processed/Public/CT_TotalSegmentator/TS_split/test/s0029.nii.gz" + }, + { + "image": "/processed/Public/CT_TotalSegmentator/s0038.nii.gz", + "label": "/processed/Public/CT_TotalSegmentator/TS_split/test/s0038.nii.gz" + }, + { + "image": "/processed/Public/CT_TotalSegmentator/s0040.nii.gz", + "label": "/processed/Public/CT_TotalSegmentator/TS_split/test/s0040.nii.gz" + }, + { + "image": "/processed/Public/CT_TotalSegmentator/s0119.nii.gz", + "label": "/processed/Public/CT_TotalSegmentator/TS_split/test/s0119.nii.gz" + }, + { + "image": "/processed/Public/CT_TotalSegmentator/s0230.nii.gz", + "label": "/processed/Public/CT_TotalSegmentator/TS_split/test/s0230.nii.gz" + }, + { + "image": "/processed/Public/CT_TotalSegmentator/s0235.nii.gz", + "label": "/processed/Public/CT_TotalSegmentator/TS_split/test/s0235.nii.gz" + }, + { + "image": "/processed/Public/CT_TotalSegmentator/s0236.nii.gz", + "label": "/processed/Public/CT_TotalSegmentator/TS_split/test/s0236.nii.gz" + }, + { + "image": "/processed/Public/CT_TotalSegmentator/s0244.nii.gz", + "label": "/processed/Public/CT_TotalSegmentator/TS_split/test/s0244.nii.gz" + }, + { + "image": "/processed/Public/CT_TotalSegmentator/s0291.nii.gz", + "label": "/processed/Public/CT_TotalSegmentator/TS_split/test/s0291.nii.gz" + }, + { + "image": "/processed/Public/CT_TotalSegmentator/s0308.nii.gz", + "label": "/processed/Public/CT_TotalSegmentator/TS_split/test/s0308.nii.gz" + }, + { + "image": "/processed/Public/CT_TotalSegmentator/s0311.nii.gz", + "label": "/processed/Public/CT_TotalSegmentator/TS_split/test/s0311.nii.gz" + }, + { + "image": "/processed/Public/CT_TotalSegmentator/s0423.nii.gz", + "label": "/processed/Public/CT_TotalSegmentator/TS_split/test/s0423.nii.gz" + }, + { + "image": "/processed/Public/CT_TotalSegmentator/s0440.nii.gz", + "label": "/processed/Public/CT_TotalSegmentator/TS_split/test/s0440.nii.gz" + }, + { + "image": "/processed/Public/CT_TotalSegmentator/s0441.nii.gz", + "label": "/processed/Public/CT_TotalSegmentator/TS_split/test/s0441.nii.gz" + }, + { + "image": "/processed/Public/CT_TotalSegmentator/s0450.nii.gz", + "label": "/processed/Public/CT_TotalSegmentator/TS_split/test/s0450.nii.gz" + }, + { + "image": "/processed/Public/CT_TotalSegmentator/s0459.nii.gz", + "label": "/processed/Public/CT_TotalSegmentator/TS_split/test/s0459.nii.gz" + }, + { + "image": "/processed/Public/CT_TotalSegmentator/s0468.nii.gz", + "label": "/processed/Public/CT_TotalSegmentator/TS_split/test/s0468.nii.gz" + }, + { + "image": "/processed/Public/CT_TotalSegmentator/s0470.nii.gz", + "label": "/processed/Public/CT_TotalSegmentator/TS_split/test/s0470.nii.gz" + }, + { + "image": "/processed/Public/CT_TotalSegmentator/s0482.nii.gz", + "label": "/processed/Public/CT_TotalSegmentator/TS_split/test/s0482.nii.gz" + }, + { + "image": "/processed/Public/CT_TotalSegmentator/s0499.nii.gz", + "label": "/processed/Public/CT_TotalSegmentator/TS_split/test/s0499.nii.gz" + }, + { + "image": "/processed/Public/CT_TotalSegmentator/s0505.nii.gz", + "label": "/processed/Public/CT_TotalSegmentator/TS_split/test/s0505.nii.gz" + }, + { + "image": "/processed/Public/CT_TotalSegmentator/s0543.nii.gz", + "label": "/processed/Public/CT_TotalSegmentator/TS_split/test/s0543.nii.gz" + }, + { + "image": "/processed/Public/CT_TotalSegmentator/s0561.nii.gz", + "label": "/processed/Public/CT_TotalSegmentator/TS_split/test/s0561.nii.gz" + }, + { + "image": "/processed/Public/CT_TotalSegmentator/s0667.nii.gz", + "label": "/processed/Public/CT_TotalSegmentator/TS_split/test/s0667.nii.gz" + }, + { + "image": "/processed/Public/CT_TotalSegmentator/s0687.nii.gz", + "label": "/processed/Public/CT_TotalSegmentator/TS_split/test/s0687.nii.gz" + }, + { + "image": "/processed/Public/CT_TotalSegmentator/s0735.nii.gz", + "label": "/processed/Public/CT_TotalSegmentator/TS_split/test/s0735.nii.gz" + }, + { + "image": "/processed/Public/CT_TotalSegmentator/s0753.nii.gz", + "label": "/processed/Public/CT_TotalSegmentator/TS_split/test/s0753.nii.gz" + }, + { + "image": "/processed/Public/CT_TotalSegmentator/s0802.nii.gz", + "label": "/processed/Public/CT_TotalSegmentator/TS_split/test/s0802.nii.gz" + }, + { + "image": "/processed/Public/CT_TotalSegmentator/s0829.nii.gz", + "label": "/processed/Public/CT_TotalSegmentator/TS_split/test/s0829.nii.gz" + }, + { + "image": "/processed/Public/CT_TotalSegmentator/s0923.nii.gz", + "label": "/processed/Public/CT_TotalSegmentator/TS_split/test/s0923.nii.gz" + }, + { + "image": "/processed/Public/CT_TotalSegmentator/s0933.nii.gz", + "label": "/processed/Public/CT_TotalSegmentator/TS_split/test/s0933.nii.gz" + }, + { + "image": "/processed/Public/CT_TotalSegmentator/s0994.nii.gz", + "label": "/processed/Public/CT_TotalSegmentator/TS_split/test/s0994.nii.gz" + }, + { + "image": "/processed/Public/CT_TotalSegmentator/s1094.nii.gz", + "label": "/processed/Public/CT_TotalSegmentator/TS_split/test/s1094.nii.gz" + }, + { + "image": "/processed/Public/CT_TotalSegmentator/s1096.nii.gz", + "label": "/processed/Public/CT_TotalSegmentator/TS_split/test/s1096.nii.gz" + }, + { + "image": "/processed/Public/CT_TotalSegmentator/s1119.nii.gz", + "label": "/processed/Public/CT_TotalSegmentator/TS_split/test/s1119.nii.gz" + }, + { + "image": "/processed/Public/CT_TotalSegmentator/s1121.nii.gz", + "label": "/processed/Public/CT_TotalSegmentator/TS_split/test/s1121.nii.gz" + }, + { + "image": "/processed/Public/CT_TotalSegmentator/s1152.nii.gz", + "label": "/processed/Public/CT_TotalSegmentator/TS_split/test/s1152.nii.gz" + }, + { + "image": "/processed/Public/CT_TotalSegmentator/s1174.nii.gz", + "label": "/processed/Public/CT_TotalSegmentator/TS_split/test/s1174.nii.gz" + }, + { + "image": "/processed/Public/CT_TotalSegmentator/s1176.nii.gz", + "label": "/processed/Public/CT_TotalSegmentator/TS_split/test/s1176.nii.gz" + }, + { + "image": "/processed/Public/CT_TotalSegmentator/s1212.nii.gz", + "label": "/processed/Public/CT_TotalSegmentator/TS_split/test/s1212.nii.gz" + }, + { + "image": "/processed/Public/CT_TotalSegmentator/s1240.nii.gz", + "label": "/processed/Public/CT_TotalSegmentator/TS_split/test/s1240.nii.gz" + }, + { + "image": "/processed/Public/CT_TotalSegmentator/s1248.nii.gz", + "label": "/processed/Public/CT_TotalSegmentator/TS_split/test/s1248.nii.gz" + }, + { + "image": "/processed/Public/CT_TotalSegmentator/s1249.nii.gz", + "label": "/processed/Public/CT_TotalSegmentator/TS_split/test/s1249.nii.gz" + }, + { + "image": "/processed/Public/CT_TotalSegmentator/s1276.nii.gz", + "label": "/processed/Public/CT_TotalSegmentator/TS_split/test/s1276.nii.gz" + }, + { + "image": "/processed/Public/CT_TotalSegmentator/s1322.nii.gz", + "label": "/processed/Public/CT_TotalSegmentator/TS_split/test/s1322.nii.gz" + }, + { + "image": "/processed/Public/CT_TotalSegmentator/s1323.nii.gz", + "label": "/processed/Public/CT_TotalSegmentator/TS_split/test/s1323.nii.gz" + }, + { + "image": "/processed/Public/CT_TotalSegmentator/s1347.nii.gz", + "label": "/processed/Public/CT_TotalSegmentator/TS_split/test/s1347.nii.gz" + }, + { + "image": "/processed/Public/CT_TotalSegmentator/s1377.nii.gz", + "label": "/processed/Public/CT_TotalSegmentator/TS_split/test/s1377.nii.gz" + }, + { + "image": "/processed/Public/CT_TotalSegmentator/s1386.nii.gz", + "label": "/processed/Public/CT_TotalSegmentator/TS_split/test/s1386.nii.gz" + } + ], + "validation": [ + { + "image": "/processed/Public/CT_TotalSegmentator/s0013.nii.gz", + "label": "/processed/Public/CT_TotalSegmentator/TS_split/test/s0013.nii.gz" + }, + { + "image": "/processed/Public/CT_TotalSegmentator/s0029.nii.gz", + "label": "/processed/Public/CT_TotalSegmentator/TS_split/test/s0029.nii.gz" + }, + { + "image": "/processed/Public/CT_TotalSegmentator/s0038.nii.gz", + "label": "/processed/Public/CT_TotalSegmentator/TS_split/test/s0038.nii.gz" + }, + { + "image": "/processed/Public/CT_TotalSegmentator/s0040.nii.gz", + "label": "/processed/Public/CT_TotalSegmentator/TS_split/test/s0040.nii.gz" + }, + { + "image": "/processed/Public/CT_TotalSegmentator/s0119.nii.gz", + "label": "/processed/Public/CT_TotalSegmentator/TS_split/test/s0119.nii.gz" + }, + { + "image": "/processed/Public/CT_TotalSegmentator/s0230.nii.gz", + "label": "/processed/Public/CT_TotalSegmentator/TS_split/test/s0230.nii.gz" + }, + { + "image": "/processed/Public/CT_TotalSegmentator/s0235.nii.gz", + "label": "/processed/Public/CT_TotalSegmentator/TS_split/test/s0235.nii.gz" + }, + { + "image": "/processed/Public/CT_TotalSegmentator/s0236.nii.gz", + "label": "/processed/Public/CT_TotalSegmentator/TS_split/test/s0236.nii.gz" + }, + { + "image": "/processed/Public/CT_TotalSegmentator/s0244.nii.gz", + "label": "/processed/Public/CT_TotalSegmentator/TS_split/test/s0244.nii.gz" + }, + { + "image": "/processed/Public/CT_TotalSegmentator/s0291.nii.gz", + "label": "/processed/Public/CT_TotalSegmentator/TS_split/test/s0291.nii.gz" + }, + { + "image": "/processed/Public/CT_TotalSegmentator/s0308.nii.gz", + "label": "/processed/Public/CT_TotalSegmentator/TS_split/test/s0308.nii.gz" + }, + { + "image": "/processed/Public/CT_TotalSegmentator/s0311.nii.gz", + "label": "/processed/Public/CT_TotalSegmentator/TS_split/test/s0311.nii.gz" + }, + { + "image": "/processed/Public/CT_TotalSegmentator/s0423.nii.gz", + "label": "/processed/Public/CT_TotalSegmentator/TS_split/test/s0423.nii.gz" + }, + { + "image": "/processed/Public/CT_TotalSegmentator/s0440.nii.gz", + "label": "/processed/Public/CT_TotalSegmentator/TS_split/test/s0440.nii.gz" + }, + { + "image": "/processed/Public/CT_TotalSegmentator/s0441.nii.gz", + "label": "/processed/Public/CT_TotalSegmentator/TS_split/test/s0441.nii.gz" + }, + { + "image": "/processed/Public/CT_TotalSegmentator/s0450.nii.gz", + "label": "/processed/Public/CT_TotalSegmentator/TS_split/test/s0450.nii.gz" + }, + { + "image": "/processed/Public/CT_TotalSegmentator/s0459.nii.gz", + "label": "/processed/Public/CT_TotalSegmentator/TS_split/test/s0459.nii.gz" + }, + { + "image": "/processed/Public/CT_TotalSegmentator/s0468.nii.gz", + "label": "/processed/Public/CT_TotalSegmentator/TS_split/test/s0468.nii.gz" + }, + { + "image": "/processed/Public/CT_TotalSegmentator/s0470.nii.gz", + "label": "/processed/Public/CT_TotalSegmentator/TS_split/test/s0470.nii.gz" + }, + { + "image": "/processed/Public/CT_TotalSegmentator/s0482.nii.gz", + "label": "/processed/Public/CT_TotalSegmentator/TS_split/test/s0482.nii.gz" + }, + { + "image": "/processed/Public/CT_TotalSegmentator/s0499.nii.gz", + "label": "/processed/Public/CT_TotalSegmentator/TS_split/test/s0499.nii.gz" + }, + { + "image": "/processed/Public/CT_TotalSegmentator/s0505.nii.gz", + "label": "/processed/Public/CT_TotalSegmentator/TS_split/test/s0505.nii.gz" + }, + { + "image": "/processed/Public/CT_TotalSegmentator/s0543.nii.gz", + "label": "/processed/Public/CT_TotalSegmentator/TS_split/test/s0543.nii.gz" + }, + { + "image": "/processed/Public/CT_TotalSegmentator/s0561.nii.gz", + "label": "/processed/Public/CT_TotalSegmentator/TS_split/test/s0561.nii.gz" + }, + { + "image": "/processed/Public/CT_TotalSegmentator/s0667.nii.gz", + "label": "/processed/Public/CT_TotalSegmentator/TS_split/test/s0667.nii.gz" + }, + { + "image": "/processed/Public/CT_TotalSegmentator/s0687.nii.gz", + "label": "/processed/Public/CT_TotalSegmentator/TS_split/test/s0687.nii.gz" + }, + { + "image": "/processed/Public/CT_TotalSegmentator/s0735.nii.gz", + "label": "/processed/Public/CT_TotalSegmentator/TS_split/test/s0735.nii.gz" + }, + { + "image": "/processed/Public/CT_TotalSegmentator/s0753.nii.gz", + "label": "/processed/Public/CT_TotalSegmentator/TS_split/test/s0753.nii.gz" + }, + { + "image": "/processed/Public/CT_TotalSegmentator/s0802.nii.gz", + "label": "/processed/Public/CT_TotalSegmentator/TS_split/test/s0802.nii.gz" + }, + { + "image": "/processed/Public/CT_TotalSegmentator/s0829.nii.gz", + "label": "/processed/Public/CT_TotalSegmentator/TS_split/test/s0829.nii.gz" + }, + { + "image": "/processed/Public/CT_TotalSegmentator/s0923.nii.gz", + "label": "/processed/Public/CT_TotalSegmentator/TS_split/test/s0923.nii.gz" + }, + { + "image": "/processed/Public/CT_TotalSegmentator/s0933.nii.gz", + "label": "/processed/Public/CT_TotalSegmentator/TS_split/test/s0933.nii.gz" + }, + { + "image": "/processed/Public/CT_TotalSegmentator/s0994.nii.gz", + "label": "/processed/Public/CT_TotalSegmentator/TS_split/test/s0994.nii.gz" + }, + { + "image": "/processed/Public/CT_TotalSegmentator/s1094.nii.gz", + "label": "/processed/Public/CT_TotalSegmentator/TS_split/test/s1094.nii.gz" + }, + { + "image": "/processed/Public/CT_TotalSegmentator/s1096.nii.gz", + "label": "/processed/Public/CT_TotalSegmentator/TS_split/test/s1096.nii.gz" + }, + { + "image": "/processed/Public/CT_TotalSegmentator/s1119.nii.gz", + "label": "/processed/Public/CT_TotalSegmentator/TS_split/test/s1119.nii.gz" + }, + { + "image": "/processed/Public/CT_TotalSegmentator/s1121.nii.gz", + "label": "/processed/Public/CT_TotalSegmentator/TS_split/test/s1121.nii.gz" + }, + { + "image": "/processed/Public/CT_TotalSegmentator/s1152.nii.gz", + "label": "/processed/Public/CT_TotalSegmentator/TS_split/test/s1152.nii.gz" + }, + { + "image": "/processed/Public/CT_TotalSegmentator/s1174.nii.gz", + "label": "/processed/Public/CT_TotalSegmentator/TS_split/test/s1174.nii.gz" + }, + { + "image": "/processed/Public/CT_TotalSegmentator/s1176.nii.gz", + "label": "/processed/Public/CT_TotalSegmentator/TS_split/test/s1176.nii.gz" + }, + { + "image": "/processed/Public/CT_TotalSegmentator/s1212.nii.gz", + "label": "/processed/Public/CT_TotalSegmentator/TS_split/test/s1212.nii.gz" + }, + { + "image": "/processed/Public/CT_TotalSegmentator/s1240.nii.gz", + "label": "/processed/Public/CT_TotalSegmentator/TS_split/test/s1240.nii.gz" + }, + { + "image": "/processed/Public/CT_TotalSegmentator/s1248.nii.gz", + "label": "/processed/Public/CT_TotalSegmentator/TS_split/test/s1248.nii.gz" + }, + { + "image": "/processed/Public/CT_TotalSegmentator/s1249.nii.gz", + "label": "/processed/Public/CT_TotalSegmentator/TS_split/test/s1249.nii.gz" + }, + { + "image": "/processed/Public/CT_TotalSegmentator/s1276.nii.gz", + "label": "/processed/Public/CT_TotalSegmentator/TS_split/test/s1276.nii.gz" + }, + { + "image": "/processed/Public/CT_TotalSegmentator/s1322.nii.gz", + "label": "/processed/Public/CT_TotalSegmentator/TS_split/test/s1322.nii.gz" + }, + { + "image": "/processed/Public/CT_TotalSegmentator/s1323.nii.gz", + "label": "/processed/Public/CT_TotalSegmentator/TS_split/test/s1323.nii.gz" + }, + { + "image": "/processed/Public/CT_TotalSegmentator/s1347.nii.gz", + "label": "/processed/Public/CT_TotalSegmentator/TS_split/test/s1347.nii.gz" + }, + { + "image": "/processed/Public/CT_TotalSegmentator/s1377.nii.gz", + "label": "/processed/Public/CT_TotalSegmentator/TS_split/test/s1377.nii.gz" + }, + { + "image": "/processed/Public/CT_TotalSegmentator/s1386.nii.gz", + "label": "/processed/Public/CT_TotalSegmentator/TS_split/test/s1386.nii.gz" + } + ] +} \ No newline at end of file diff --git a/models/pediatric_abdominal_ct_segmentation/configs/evaluate-standalone-parallel.yaml b/models/pediatric_abdominal_ct_segmentation/configs/evaluate-standalone-parallel.yaml new file mode 100644 index 00000000..be5f3a49 --- /dev/null +++ b/models/pediatric_abdominal_ct_segmentation/configs/evaluate-standalone-parallel.yaml @@ -0,0 +1,18 @@ +--- +imports: +- "$import glob" +- "$import json" +- "$import os" +- "$from scripts.compute_metric import compute_abdominal_ct_metrics" +- "$from scripts.compute_metric import compute" +workflow_type: evaluate +spatial_dims: "$len(@spatial_size)" +bundle_root: "." +output_dir: "$@bundle_root + '/eval/dynunet_FT_trt_32'" +dataset_dir: "/processed/Public/CT_TotalSegmentator/TS_split/test/" +data_list_file_path: "$@bundle_root + '/configs/TS_test.json'" +datalist: "$monai.data.load_decathlon_datalist(@data_list_file_path, data_list_key='validation')" +datalist_pred: "$[{**d, 'pred': os.path.join(@output_dir, d['label'].split('/')[-1].split('.')[0] + '_trans.nii.gz')} for d in @datalist]" +run: +#- "$compute_abdominal_ct_metrics(@datalist_pred, @output_dir)" +- "$compute(@datalist_pred, @output_dir)" \ No newline at end of file diff --git a/models/pediatric_abdominal_ct_segmentation/configs/evaluate-standalone.yaml b/models/pediatric_abdominal_ct_segmentation/configs/evaluate-standalone.yaml new file mode 100644 index 00000000..99201ec3 --- /dev/null +++ b/models/pediatric_abdominal_ct_segmentation/configs/evaluate-standalone.yaml @@ -0,0 +1,17 @@ +--- +imports: +- "$import glob" +- "$import json" +- "$import os" +- "$from scripts.compute_metric import compute_abdominal_ct_metrics" +- "$from scripts.compute_metric import compute" +workflow_type: evaluate +spatial_dims: "$len(@spatial_size)" +bundle_root: "." +output_dir: "$@bundle_root + '/eval/dynunet_FT_trt_32'" +dataset_dir: "/processed/Public/CT_TotalSegmentator/TS_split/test/" +data_list_file_path: "$@bundle_root + '/configs/TS_test.json'" +datalist: "$monai.data.load_decathlon_datalist(@data_list_file_path, data_list_key='validation')" +datalist_pred: "$[{**d, 'pred': os.path.join(@output_dir, d['label'].split('/')[-1].split('.')[0] + '_trans.nii.gz')} for d in @datalist]" +run: +- "$compute_single_node(@datalist_pred, @output_dir)" \ No newline at end of file diff --git a/models/pediatric_abdominal_ct_segmentation/configs/evaluate.yaml b/models/pediatric_abdominal_ct_segmentation/configs/evaluate.yaml new file mode 100644 index 00000000..32a0e086 --- /dev/null +++ b/models/pediatric_abdominal_ct_segmentation/configs/evaluate.yaml @@ -0,0 +1,63 @@ +--- +validate#postprocessing: + _target_: Compose + transforms: + - _target_: Activationsd + keys: pred + softmax: true + - _target_: Invertd + keys: + - pred + - label + transform: "@validate#preprocessing" + orig_keys: image + meta_key_postfix: meta_dict + nearest_interp: + - false + - true + to_tensor: true + - _target_: AsDiscreted + keys: + - pred + - label + argmax: + - true + - false + to_onehot: 4 + - _target_: CopyItemsd + keys: "pred" + times: 1 + names: "pred_save" + - _target_: AsDiscreted + keys: + - pred_save + argmax: + - true + - _target_: SaveImaged + keys: pred_save + meta_keys: pred_meta_dict + output_dir: "@output_dir" + resample: false + squeeze_end_dims: true +validate#dataset: + _target_: Dataset + data: "@val_datalist" + transform: "@validate#preprocessing" +validate#handlers: +- _target_: CheckpointLoader + load_path: "$@ckpt_dir + '/dynunet_FT.pt'" + load_dict: + model: "@network" +- _target_: StatsHandler + iteration_log: false +- _target_: MetricsSaver + save_dir: "@output_dir" + metrics: val_dice + metric_details: + - val_dice + #batch_transform: "$monai.handlers.from_engine(['image_meta_dict'])" + summary_ops: "*" +initialize: +- "$setattr(torch.backends.cudnn, 'benchmark', True)" +run: +- "$@validate#evaluator.run()" diff --git a/models/pediatric_abdominal_ct_segmentation/configs/inference.yaml b/models/pediatric_abdominal_ct_segmentation/configs/inference.yaml new file mode 100644 index 00000000..c7ad44a7 --- /dev/null +++ b/models/pediatric_abdominal_ct_segmentation/configs/inference.yaml @@ -0,0 +1,161 @@ +--- +imports: +- "$import glob" +- "$import os" +- "$import scripts.monai_utils" +workflow_type: inference +input_channels: 1 +output_classes: 4 +output_channels: 4 +# arch_ckpt_path: "$@bundle_root + '/models/dynunet_FT.pt'" +# arch_ckpt: "$torch.load(@arch_ckpt_path, map_location=torch.device('cuda'))" +bundle_root: "." +output_dir: "$@bundle_root + '/eval/dynunet_FT'" +dataset_dir: "/processed/Public/CT_TotalSegmentator/TS_split/test/" +data_list_file_path: "$@bundle_root + '/configs/TS_test.json'" +datalist: "$monai.data.load_decathlon_datalist(@data_list_file_path, data_list_key='validation')" +device: "$torch.device('cuda:0' if torch.cuda.is_available() else 'cpu')" +spatial_size: +- 96 +- 96 +- 96 +spatial_dims: "$len(@spatial_size)" +labels: + background: 0 + liver: 1 + spleen: 2 + pancreas: 3 +network_def: + _target_: monai.networks.nets.DynUNet + spatial_dims: "@spatial_dims" + in_channels: "@input_channels" + out_channels: "@output_channels" + kernel_size: + - 3 + - 3 + - 3 + - 3 + - 3 + - 3 + strides: + - 1 + - 2 + - 2 + - 2 + - 2 + - + - 2 + - 2 + - 1 + upsample_kernel_size: + - 2 + - 2 + - 2 + - 2 + - + - 2 + - 2 + - 1 + norm_name: "instance" + deep_supervision: false + res_block: true +network: "$@network_def.to(@device)" +image_key: image +preprocessing: + _target_: Compose + transforms: + - _target_: LoadImaged + keys: "@image_key" + reader: ITKReader + - _target_: EnsureChannelFirstd + keys: "@image_key" + - _target_: Orientationd + keys: image + axcodes: RAS + - _target_: Spacingd + keys: + - "@image_key" + pixdim: + - 1.5 + - 1.5 + - 3.0 + mode: + - bilinear + - _target_: ScaleIntensityRanged + keys: "@image_key" + a_min: -250 + a_max: 400 + b_min: 0 + b_max: 1 + clip: true + - _target_: CropForegroundd + keys: + - "@image_key" + source_key: "@image_key" + mode: + - "minimum" + - _target_: EnsureTyped + keys: image + - _target_: CastToTyped + keys: "@image_key" + dtype: "$torch.float32" +dataset: + _target_: Dataset + data: "@datalist" + transform: "@preprocessing" +dataloader: + _target_: DataLoader + dataset: "@dataset" + batch_size: 1 + shuffle: false + num_workers: 4 +inferer: + _target_: SlidingWindowInferer + roi_size: + - 96 + - 96 + - 96 + sw_batch_size: 4 + overlap: 0.75 +postprocessing: + _target_: Compose + transforms: + - _target_: Activationsd + keys: pred + softmax: true + - _target_: Invertd + keys: pred + transform: "@preprocessing" + orig_keys: image + meta_key_postfix: meta_dict + nearest_interp: false + to_tensor: true + - _target_: AsDiscreted + keys: pred + argmax: true + - _target_: SaveImaged + keys: pred + meta_keys: pred_meta_dict + output_dir: "@output_dir" + separate_folder: false + output_dtype: "$torch.int16" +handlers: +- _target_: CheckpointLoader + load_path: "$@bundle_root + '/models/dynunet_FT.pt'" + load_dict: + model: "@network" +- _target_: StatsHandler + iteration_log: false +evaluator: + _target_: SupervisedEvaluator + device: "@device" + val_data_loader: "@dataloader" + network: "@network" + inferer: "@inferer" + postprocessing: "@postprocessing" + val_handlers: "@handlers" + amp: true +initialize: +- "$setattr(torch.backends.cudnn, 'benchmark', True)" +run: +- "$@evaluator.run()" \ No newline at end of file diff --git a/models/pediatric_abdominal_ct_segmentation/configs/inference_segresnet.yaml b/models/pediatric_abdominal_ct_segmentation/configs/inference_segresnet.yaml new file mode 100644 index 00000000..8d09fbc4 --- /dev/null +++ b/models/pediatric_abdominal_ct_segmentation/configs/inference_segresnet.yaml @@ -0,0 +1,140 @@ +--- +imports: +- "$import glob" +- "$import os" +- "$import scripts.monai_utils" +workflow_type: inference +input_channels: 1 +output_classes: 4 +output_channels: 4 +bundle_root: "." +output_dir: "$@bundle_root + '/eval/segresnet_FT'" +dataset_dir: "/processed/Public/CT_TotalSegmentator/TS_split/test/" +data_list_file_path: "$@bundle_root + '/configs/TS_test.json'" +datalist: "$monai.data.load_decathlon_datalist(@data_list_file_path, data_list_key='validation')" +device: "$torch.device('cuda:0' if torch.cuda.is_available() else 'cpu')" +spatial_size: +- 96 +- 96 +- 96 +spatial_dims: "$len(@spatial_size)" +labels: + background: 0 + liver: 1 + spleen: 2 + pancreas: 3 +network_def: + _target_: monai.networks.nets.SegResNet + blocks_down: + - 1 + - 2 + - 2 + - 4 + blocks_up: + - 1 + - 1 + - 1 + init_filters: 16 + in_channels: "@input_channels" + out_channels: "@output_channels" + dropout_prob: 0.0 +network: "$@network_def.to(@device)" +image_key: image +preprocessing: + _target_: Compose + transforms: + - _target_: LoadImaged + keys: "@image_key" + reader: ITKReader + - _target_: EnsureChannelFirstd + keys: "@image_key" + - _target_: Orientationd + keys: image + axcodes: RAS + - _target_: Spacingd + keys: + - "@image_key" + pixdim: + - 1.5 + - 1.5 + - 3.0 + mode: + - bilinear + - _target_: ScaleIntensityRanged + keys: "@image_key" + a_min: -250 + a_max: 400 + b_min: 0 + b_max: 1 + clip: true + - _target_: CropForegroundd + keys: + - "@image_key" + source_key: "@image_key" + mode: + - "minimum" + - _target_: EnsureTyped + keys: image + - _target_: CastToTyped + keys: "@image_key" + dtype: "$torch.float32" +dataset: + _target_: Dataset + data: "@datalist" + transform: "@preprocessing" +dataloader: + _target_: DataLoader + dataset: "@dataset" + batch_size: 1 + shuffle: false + num_workers: 4 +inferer: + _target_: SlidingWindowInferer + roi_size: + - 96 + - 96 + - 96 + sw_batch_size: 4 + overlap: 0.75 +postprocessing: + _target_: Compose + transforms: + - _target_: Activationsd + keys: pred + softmax: true + - _target_: Invertd + keys: pred + transform: "@preprocessing" + orig_keys: image + meta_key_postfix: meta_dict + nearest_interp: false + to_tensor: true + - _target_: AsDiscreted + keys: pred + argmax: true + - _target_: SaveImaged + keys: pred + meta_keys: pred_meta_dict + output_dir: "@output_dir" + separate_folder: false + output_dtype: "$torch.int16" +handlers: +- _target_: CheckpointLoader + load_path: "$@bundle_root + '/models/segresnet_FT.pt'" + load_dict: + model: "@network" +- _target_: StatsHandler + iteration_log: false +evaluator: + _target_: SupervisedEvaluator + device: "@device" + val_data_loader: "@dataloader" + network: "@network" + inferer: "@inferer" + postprocessing: "@postprocessing" + val_handlers: "@handlers" + amp: true +initialize: +- "$setattr(torch.backends.cudnn, 'benchmark', True)" +run: +- "$@evaluator.run()" \ No newline at end of file diff --git a/models/pediatric_abdominal_ct_segmentation/configs/inference_swinunetr.yaml b/models/pediatric_abdominal_ct_segmentation/configs/inference_swinunetr.yaml new file mode 100644 index 00000000..5ba2ce9f --- /dev/null +++ b/models/pediatric_abdominal_ct_segmentation/configs/inference_swinunetr.yaml @@ -0,0 +1,137 @@ +--- +imports: +- "$import glob" +- "$import os" +- "$import scripts.monai_utils" +workflow_type: inference +input_channels: 1 +output_classes: 4 +output_channels: 4 +# arch_ckpt_path: "$@bundle_root + '/models/dynunet_FT.pt'" +# arch_ckpt: "$torch.load(@arch_ckpt_path, map_location=torch.device('cuda'))" +bundle_root: "." +output_dir: "$@bundle_root + '/eval/swinunetr_FT'" +dataset_dir: "/processed/Public/CT_TotalSegmentator/TS_split/test/" +data_list_file_path: "$@bundle_root + '/configs/TS_test.json'" +datalist: "$monai.data.load_decathlon_datalist(@data_list_file_path, data_list_key='validation')" +device: "$torch.device('cuda:0' if torch.cuda.is_available() else 'cpu')" +spatial_size: +- 96 +- 96 +- 96 +spatial_dims: "$len(@spatial_size)" +labels: + background: 0 + liver: 1 + spleen: 2 + pancreas: 3 +network_def: + _target_: monai.networks.nets.SwinUNETR + img_size: "@spatial_size" + in_channels: "@input_channels" + out_channels: "@output_channels" + feature_size: 48 + drop_rate: 0.0 + attn_drop_rate: 0.0 + dropout_path_rate: 0.0 + use_checkpoint: false +network: "$@network_def.to(@device)" +image_key: image +preprocessing: + _target_: Compose + transforms: + - _target_: LoadImaged + keys: "@image_key" + reader: ITKReader + - _target_: EnsureChannelFirstd + keys: "@image_key" + - _target_: Orientationd + keys: image + axcodes: RAS + - _target_: Spacingd + keys: + - "@image_key" + pixdim: + - 1.5 + - 1.5 + - 3.0 + mode: + - bilinear + - _target_: ScaleIntensityRanged + keys: "@image_key" + a_min: -250 + a_max: 400 + b_min: 0 + b_max: 1 + clip: true + - _target_: CropForegroundd + keys: + - "@image_key" + source_key: "@image_key" + mode: + - "minimum" + - _target_: EnsureTyped + keys: image + - _target_: CastToTyped + keys: "@image_key" + dtype: "$torch.float32" +dataset: + _target_: Dataset + data: "@datalist" + transform: "@preprocessing" +dataloader: + _target_: DataLoader + dataset: "@dataset" + batch_size: 1 + shuffle: false + num_workers: 4 +inferer: + _target_: SlidingWindowInferer + roi_size: + - 96 + - 96 + - 96 + sw_batch_size: 4 + overlap: 0.75 +postprocessing: + _target_: Compose + transforms: + - _target_: Activationsd + keys: pred + softmax: true + - _target_: Invertd + keys: pred + transform: "@preprocessing" + orig_keys: image + meta_key_postfix: meta_dict + nearest_interp: false + to_tensor: true + - _target_: AsDiscreted + keys: pred + argmax: true + - _target_: SaveImaged + keys: pred + meta_keys: pred_meta_dict + output_dir: "@output_dir" + separate_folder: false + output_dtype: "$torch.int16" +handlers: +- _target_: CheckpointLoader + load_path: "$@bundle_root + '/models/swinunetr_FT.pt'" + load_dict: + model: "@network" +- _target_: StatsHandler + iteration_log: false +evaluator: + _target_: SupervisedEvaluator + device: "@device" + val_data_loader: "@dataloader" + network: "@network" + inferer: "@inferer" + postprocessing: "@postprocessing" + val_handlers: "@handlers" + amp: true +initialize: +- "$setattr(torch.backends.cudnn, 'benchmark', True)" +run: +- "$@evaluator.run()" \ No newline at end of file diff --git a/models/pediatric_abdominal_ct_segmentation/configs/inference_trt.yaml b/models/pediatric_abdominal_ct_segmentation/configs/inference_trt.yaml new file mode 100644 index 00000000..3a2bbbb8 --- /dev/null +++ b/models/pediatric_abdominal_ct_segmentation/configs/inference_trt.yaml @@ -0,0 +1,8 @@ +--- +imports: +- "$import glob" +- "$import os" +- "$import torch_tensorrt" +handlers#0#_disabled_: true +network_def: "$torch.jit.load(@bundle_root + '/models/A100/dynunet_FT_trt_16.ts')" +evaluator#amp: false diff --git a/models/pediatric_abdominal_ct_segmentation/configs/logging.conf b/models/pediatric_abdominal_ct_segmentation/configs/logging.conf new file mode 100644 index 00000000..db85a0b9 --- /dev/null +++ b/models/pediatric_abdominal_ct_segmentation/configs/logging.conf @@ -0,0 +1,21 @@ +[loggers] +keys=root + +[handlers] +keys=consoleHandler + +[formatters] +keys=fullFormatter + +[logger_root] +level=INFO +handlers=consoleHandler + +[handler_consoleHandler] +class=StreamHandler +level=INFO +formatter=fullFormatter +args=(sys.stdout,) + +[formatter_fullFormatter] +format=%(asctime)s - %(name)s - %(levelname)s - %(message)s \ No newline at end of file diff --git a/models/pediatric_abdominal_ct_segmentation/configs/metadata.json b/models/pediatric_abdominal_ct_segmentation/configs/metadata.json new file mode 100644 index 00000000..24fa85a1 --- /dev/null +++ b/models/pediatric_abdominal_ct_segmentation/configs/metadata.json @@ -0,0 +1,101 @@ +{ + "schema": "https://github.com/Project-MONAI/MONAI-extra-test-data/releases/download/0.8.1/meta_schema_20220324.json", + "version": "0.4.3", + "changelog": { + "0.4.3": "add support for TensorRT conversion and inference", + "0.4.2": "update search function to match monai 1.2", + "0.4.1": "fix the wrong GPU index issue of multi-node", + "0.4.0": "remove error dollar symbol in readme", + "0.3.9": "add cpu ram requirement in readme", + "0.3.8": "add non-deterministic note", + "0.3.7": "re-train model with updated dints implementation", + "0.3.6": "black autofix format and add name tag", + "0.3.5": "restructure readme to match updated template", + "0.3.4": "correct typos", + "0.3.3": "update learning rate and readme", + "0.3.2": "update to use monai 1.0.1", + "0.3.1": "fix license Copyright error", + "0.3.0": "update license files", + "0.2.0": "unify naming", + "0.1.1": "fix data type issue in searching/training configurations", + "0.1.0": "complete the model package", + "0.0.1": "initialize the model package structure" + }, + "monai_version": "1.3.0", + "pytorch_version": "2.1.0", + "numpy_version": "1.22.2", + "optional_packages_version": { + "fire": "0.4.0", + "nibabel": "4.0.1", + "pytorch-ignite": "0.4.9" + }, + "name": "CT-Ped-Abdominal-Seg", + "task": "Training and Prediction of 3D Segmentation of Liver, Spleen and Pancreas from Abdominal CT images", + "description": "TotalSegmentator, TCIA and BTCV dataset pre-trained model for segmenting liver, spleen and pancreas, fine-tuned on Cincinnati Children's Healthy Pediatric Dataset with High Quality Masks. WandB hyperparameter search was used to find the best hyperparameters for training.", + "authors": "Cincinnati Children's (CCHMC) - CAIIR Center (https://www.cincinnatichildrens.org/research/divisions/r/radiology/labs/caiir)", + "copyright": "Copyright (c) MONAI Consortium", + "data_source": "TotalSegmentator, TCIA and BTCV dataset public data", + "data_type": "nifti", + "image_classes": "single channel 3D data HU thresholded and clipped to a range of 0 to 1", + "label_classes": "single channel data, 1 is liver, 2 is spleen, 3 is pancreas and 0 is everything else", + "pred_classes": "single channel data, 1 is liver, 2 is spleen, 3 is pancreas and 0 is everything else", + "eval_metrics": { + "TS_mean_dice": 0.90, + "TCIA_mean_dice": 0.87, + "CCHMC_mean_dice": 0.89 + }, + "intended_use": "Pediatric model - Validation on institutional data required before clinical use", + "references": [ + "MedArxiv paper: url to be updated" + ], + "network_data_format": { + "inputs": { + "image": { + "type": "image", + "format": "hounsfield", + "modality": "CT", + "num_channels": 1, + "spatial_shape": [ + 96, + 96, + 96 + ], + "dtype": "float32", + "value_range": [ + 0, + 1 + ], + "is_patch_data": true, + "channel_def": { + "0": "image" + } + } + }, + "outputs": { + "pred": { + "type": "image", + "format": "segmentation", + "num_channels": 4, + "spatial_shape": [ + 96, + 96, + 96 + ], + "dtype": "float32", + "value_range": [ + 0, + 1, + 2, + 3 + ], + "is_patch_data": true, + "label_def": { + "0": "background", + "1": "liver", + "2": "spleen", + "3": "pancreas" + } + } + } + } +} \ No newline at end of file diff --git a/models/pediatric_abdominal_ct_segmentation/configs/train-multigpu.yaml b/models/pediatric_abdominal_ct_segmentation/configs/train-multigpu.yaml new file mode 100644 index 00000000..1d094b4b --- /dev/null +++ b/models/pediatric_abdominal_ct_segmentation/configs/train-multigpu.yaml @@ -0,0 +1,372 @@ +--- +imports: +- "$import glob" +- "$import json" +- "$import os" +- "$import ignite" +- "$from scipy import ndimage" +- $import scripts +- $import scripts.monai_utils +- $import scripts.lr_scheduler +- $import scripts.utils +- $from monai.data.utils import list_data_collate +- "$import monai.apps.deepedit.transforms" +workflow_type: train +input_channels: 1 +output_channels: 4 +output_classes: 4 +#arch_ckpt_path: "$@bundle_root + '/models/dynunet_FT.pt'" +#arch_ckpt: "$torch.load(@arch_ckpt_path, map_location=torch.device('cuda'))" +bundle_root: "." +ckpt_dir: "$@bundle_root + '/models'" +output_dir: "$@bundle_root + '/eval'" +dataset_dir: "/processed/Public/CT_TotalSegmentator/TS_split/test/" #"/workspace/data" +data_list_file_path: "$@bundle_root + '/configs/TS_test.json'" +train_datalist: "$monai.data.load_decathlon_datalist(@data_list_file_path, data_list_key='training')" +val_datalist: "$monai.data.load_decathlon_datalist(@data_list_file_path, data_list_key='validation')" +n_gpu: +- 0 +- 1 +device: "$torch.device('cuda:' + str(@n_gpu[0]) if torch.cuda.is_available() else 'cpu')" +device_list: "$scripts.monai_utils.get_device_list(@n_gpu)" +spatial_size: +- 96 +- 96 +- 96 +spatial_dims: "$len(@spatial_size)" +labels: + background: 0 + liver: 1 + spleen: 2 + pancreas: 3 +network_def: + _target_: monai.networks.nets.DynUNet + spatial_dims: "@spatial_dims" + in_channels: "@input_channels" + out_channels: "@output_channels" + kernel_size: + - 3 + - 3 + - 3 + - 3 + - 3 + - 3 + strides: + - 1 + - 2 + - 2 + - 2 + - 2 + - + - 2 + - 2 + - 1 + upsample_kernel_size: + - 2 + - 2 + - 2 + - 2 + - + - 2 + - 2 + - 1 + norm_name: "instance" + deep_supervision: false + res_block: true +network: "$@network_def.to(@device)" +loss: + _target_: DiceCELoss + include_background: false + to_onehot_y: true + softmax: true + squared_pred: true + batch: true + smooth_nr: 1.0e-06 + smooth_dr: 1.0e-06 +optimizer: + _target_: torch.optim.AdamW + params: "$@network.parameters()" + weight_decay: 1.0e-05 + lr: 0.00005 +max_epochs: 15 +lr_scheduler: + _target_: scripts.lr_scheduler.LinearWarmupCosineAnnealingLR + optimizer: "@optimizer" + warmup_epochs: 10 + warmup_start_lr: 0.0000005 + eta_min: 1.0e-08 + max_epochs: "@max_epochs" +image_key: image +label_key: label +val_interval: 2 +train: + deterministic_transforms: + - _target_: LoadImaged + keys: + - "@image_key" + - "@label_key" + reader: ITKReader + - _target_: EnsureChannelFirstd + keys: + - "@image_key" + - "@label_key" + - _target_: Orientationd + keys: + - "@image_key" + - "@label_key" + axcodes: RAS + - _target_: Spacingd + keys: + - "@image_key" + - "@label_key" + pixdim: + - 1.5 + - 1.5 + - 3.0 + mode: + - bilinear + - nearest + - _target_: scripts.monai_utils.AddLabelNamesd # monai.apps.deepedit.transforms + #_mode_: "debug" + keys: "@label_key" + label_names: "@labels" + - _target_: ScaleIntensityRanged + keys: "@image_key" + a_min: -250 + a_max: 400 + b_min: 0 + b_max: 1 + clip: true + - _target_: CropForegroundd + keys: + - "@image_key" + - "@label_key" + source_key: "@image_key" + mode: + - "minimum" + - "minimum" + - _target_: EnsureTyped + keys: + - "@image_key" + - "@label_key" + - _target_: CastToTyped + keys: "@image_key" + dtype: "$torch.float32" + random_transforms: + - _target_: RandCropByLabelClassesd + keys: + - "@image_key" + - "@label_key" + label_key: "@label_key" # label4crop + spatial_size: "@spatial_size" + num_classes: 4 + ratios: null + allow_smaller: true + num_samples: 8 +# - _target_: RandSpatialCropSamplesd +# keys: +# - "@image_key" +# - "@label_key" +# roi_size: "$[int(x * 0.75) for x in @spatial_size]" +# num_samples: 1 +# max_roi_size: "@spatial_size" +# random_center: true +# random_size: true +# allow_missing_keys: false + - _target_: SpatialPadd + keys: + - "@image_key" + - "@label_key" + spatial_size: "@spatial_size" + method: "symmetric" + mode: + - "minimum" + - "minimum" + allow_missing_keys: false + - _target_: RandRotate90d + keys: + - "@image_key" + - "@label_key" + prob: 0.5 + max_k: 3 + allow_missing_keys: false + - _target_: SelectItemsd + keys: + - "@image_key" + - "@label_key" + - "label_names" + - _target_: CastToTyped + keys: + - "@image_key" + - "@label_key" + dtype: + - "$torch.float32" + - "$torch.uint8" + - _target_: ToTensord + keys: + - "@image_key" + - "@label_key" + preprocessing: + _target_: Compose + transforms: "$@train#deterministic_transforms + @train#random_transforms" + dataset: + _target_: PersistentDataset + data: "@train_datalist" + transform: "@train#preprocessing" + cache_dir: "$@bundle_root + '/cache'" + dataloader: + _target_: DataLoader + dataset: "@train#dataset" + batch_size: 2 + shuffle: true + num_workers: 4 + collate_fn: $list_data_collate + inferer: + _target_: SimpleInferer + postprocessing: + _target_: Compose + transforms: + - _target_: Activationsd + keys: pred + softmax: true + - _target_: AsDiscreted + keys: + - pred + - label + argmax: + - true + - false + to_onehot: + - "@output_classes" + - "@output_classes" + - _target_: scripts.monai_utils.SplitPredsLabeld # monai.apps.deepedit.transforms + keys: pred +# dice_function: +# _target_: "$engine.state.metrics['train_dice']" + handlers: + - _target_: LrScheduleHandler + lr_scheduler: "@lr_scheduler" + print_lr: true +# step_transform: "@dice_function" + - _target_: ValidationHandler + validator: "@validate#evaluator" + epoch_level: true + interval: "@val_interval" + - _target_: StatsHandler + tag_name: train_loss + output_transform: "$monai.handlers.from_engine(['loss'], first=True)" + - _target_: TensorBoardStatsHandler + log_dir: "@output_dir" + tag_name: train_loss + output_transform: "$monai.handlers.from_engine(['loss'], first=True)" + key_metric: + train_dice: + _target_: MeanDice + output_transform: "$monai.handlers.from_engine(['pred', 'label'])" + include_background: false + additional_metrics: + liver_dice: + _target_: monai.handlers.MeanDice + output_transform: "$monai.handlers.from_engine(['pred_liver', 'label_liver'])" + include_background: false + spleen_dice: + _target_: monai.handlers.MeanDice + output_transform: "$monai.handlers.from_engine(['pred_spleen', 'label_spleen'])" + include_background: false + pancreas_dice: + _target_: monai.handlers.MeanDice + output_transform: "$monai.handlers.from_engine(['pred_pancreas', 'label_pancreas'])" + include_background: false + trainer: + _target_: scripts.monai_utils.SupervisedTrainer_m_gpu # SupervisedTrainer + device: "@device_list" # "@device" + max_epochs: "@max_epochs" + train_data_loader: "@train#dataloader" + network: "@network" + loss_function: "@loss" + # train_interaction: null + optimizer: "@optimizer" + inferer: "@train#inferer" + postprocessing: "@train#postprocessing" + key_train_metric: "@train#key_metric" + additional_metrics: "@train#additional_metrics" + train_handlers: "@train#handlers" + amp: true +validate: + preprocessing: + _target_: Compose + transforms: "%train#deterministic_transforms" + dataset: + # _target_: CacheDataset + # data: "@val_datalist" + # transform: "@validate#preprocessing" + # cache_rate: 0.025 + _target_: PersistentDataset + data: "@val_datalist" + transform: "@validate#preprocessing" + cache_dir: "$@bundle_root + '/cache'" + dataloader: + _target_: DataLoader + dataset: "@validate#dataset" + batch_size: 1 + shuffle: false + num_workers: 4 + collate_fn: $list_data_collate + inferer: + _target_: SlidingWindowInferer + roi_size: "@spatial_size" + sw_batch_size: 4 + mode: "constant" + overlap: 0.5 + postprocessing: "%train#postprocessing" + handlers: + - _target_: StatsHandler + iteration_log: false + - _target_: TensorBoardStatsHandler + log_dir: "@output_dir" + iteration_log: false + - _target_: CheckpointSaver + save_dir: "@ckpt_dir" + save_dict: + model: "@network" + save_key_metric: true + key_metric_filename: model_latest.pt + key_metric: + val_dice: + _target_: MeanDice + output_transform: "$monai.handlers.from_engine(['pred', 'label'])" + include_background: false + additional_metrics: + val_liver_dice: + _target_: monai.handlers.MeanDice + output_transform: "$monai.handlers.from_engine(['pred_liver', 'label_liver'])" + include_background: false + val_spleen_dice: + _target_: monai.handlers.MeanDice + output_transform: "$monai.handlers.from_engine(['pred_spleen', 'label_spleen'])" + include_background: false + val_pancreas_dice: + _target_: monai.handlers.MeanDice + output_transform: "$monai.handlers.from_engine(['pred_pancreas', 'label_pancreas'])" + include_background: false + evaluator: + _target_: SupervisedEvaluator + device: "@device" + val_data_loader: "@validate#dataloader" + network: "@network" + inferer: "@validate#inferer" + postprocessing: "@validate#postprocessing" + key_val_metric: "@validate#key_metric" + additional_metrics: "@validate#additional_metrics" + val_handlers: "@validate#handlers" + amp: true +initialize: +- "$monai.utils.set_determinism(seed=123)" +run: +- "$print('Training started... ')" +- "$print('output_channels: ', @output_channels )" +- "$print('spatial_dims: ', @spatial_dims)" +- "$print('Labels dict: ', @labels)" +- "$print('Get device list: ', scripts.monai_utils.get_device_list(@n_gpu))" +#- "$[print(i,': ', data['image'].shape) for i, data in enumerate(@train#dataloader)]" +- "$@train#trainer.run()" \ No newline at end of file diff --git a/models/pediatric_abdominal_ct_segmentation/configs/train.yaml b/models/pediatric_abdominal_ct_segmentation/configs/train.yaml new file mode 100644 index 00000000..3d9c0c6b --- /dev/null +++ b/models/pediatric_abdominal_ct_segmentation/configs/train.yaml @@ -0,0 +1,361 @@ +--- +imports: +- "$import glob" +- "$import json" +- "$import os" +- "$import ignite" +- "$from scipy import ndimage" +- "$import scripts.monai_utils" +- "$import scripts.lr_scheduler" +- "$import monai.apps.deepedit.transforms" +workflow_type: train +input_channels: 1 +output_channels: 4 +output_classes: 4 +#arch_ckpt_path: "$@bundle_root + '/models/dynunet_FT.pt'" +#arch_ckpt: "$torch.load(@arch_ckpt_path, map_location=torch.device('cuda'))" +bundle_root: "." +ckpt_dir: "$@bundle_root + '/models'" +output_dir: "$@bundle_root + '/eval'" +dataset_dir: "/processed/Public/CT_TotalSegmentator/TS_split/test/" #"/workspace/data" +data_list_file_path: "$@bundle_root + '/configs/TS_test.json'" +train_datalist: "$monai.data.load_decathlon_datalist(@data_list_file_path, data_list_key='training')" +val_datalist: "$monai.data.load_decathlon_datalist(@data_list_file_path, data_list_key='validation')" +n_gpu: 0 +device: "$torch.device('cuda:' + str(@n_gpu) if torch.cuda.is_available() else 'cpu')" +spatial_size: +- 96 +- 96 +- 96 +spatial_dims: "$len(@spatial_size)" +labels: + background: 0 + liver: 1 + spleen: 2 + pancreas: 3 +network_def: + _target_: monai.networks.nets.DynUNet + spatial_dims: "@spatial_dims" + in_channels: "@input_channels" + out_channels: "@output_channels" + kernel_size: + - 3 + - 3 + - 3 + - 3 + - 3 + - 3 + strides: + - 1 + - 2 + - 2 + - 2 + - 2 + - + - 2 + - 2 + - 1 + upsample_kernel_size: + - 2 + - 2 + - 2 + - 2 + - + - 2 + - 2 + - 1 + norm_name: "instance" + deep_supervision: false + res_block: true +network: "$@network_def.to(@device)" +loss: + _target_: DiceCELoss + include_background: false + to_onehot_y: true + softmax: true + squared_pred: true + batch: true + smooth_nr: 1.0e-06 + smooth_dr: 1.0e-06 +optimizer: + _target_: torch.optim.AdamW + params: "$@network.parameters()" + weight_decay: 1.0e-05 + lr: 0.00005 +max_epochs: 15 +lr_scheduler: + _target_: scripts.lr_scheduler.LinearWarmupCosineAnnealingLR + optimizer: "@optimizer" + warmup_epochs: 10 + warmup_start_lr: 0.0000005 + eta_min: 1.0e-08 + max_epochs: "@max_epochs" +image_key: image +label_key: label +val_interval: 2 +train: + deterministic_transforms: + - _target_: LoadImaged + keys: + - "@image_key" + - "@label_key" + reader: ITKReader + - _target_: EnsureChannelFirstd + keys: + - "@image_key" + - "@label_key" + - _target_: Orientationd + keys: + - "@image_key" + - "@label_key" + axcodes: RAS + - _target_: Spacingd + keys: + - "@image_key" + - "@label_key" + pixdim: + - 1.5 + - 1.5 + - 3.0 + mode: + - bilinear + - nearest + - _target_: scripts.monai_utils.AddLabelNamesd + keys: "@label_key" + label_names: "@labels" + - _target_: ScaleIntensityRanged + keys: "@image_key" + a_min: -250 + a_max: 400 + b_min: 0 + b_max: 1 + clip: true + - _target_: CropForegroundd + keys: + - "@image_key" + - "@label_key" + source_key: "@image_key" + mode: + - "minimum" + - "minimum" + - _target_: EnsureTyped + keys: + - "@image_key" + - "@label_key" + - _target_: CastToTyped + keys: "@image_key" + dtype: "$torch.float32" + random_transforms: + - _target_: RandCropByLabelClassesd + keys: + - "@image_key" + - "@label_key" + label_key: "@label_key" # label4crop + spatial_size: "@spatial_size" + num_classes: 4 + ratios: null + allow_smaller: true + num_samples: 8 +# - _target_: RandSpatialCropSamplesd +# keys: +# - "@image_key" +# - "@label_key" +# roi_size: "$[int(x * 0.75) for x in @spatial_size]" +# num_samples: 1 +# max_roi_size: "@spatial_size" +# random_center: true +# random_size: true +# allow_missing_keys: false + - _target_: SpatialPadd + keys: + - "@image_key" + - "@label_key" + spatial_size: "@spatial_size" + method: "symmetric" + mode: + - "minimum" + - "minimum" + allow_missing_keys: false + - _target_: RandRotate90d + keys: + - "@image_key" + - "@label_key" + prob: 0.5 + max_k: 3 + allow_missing_keys: false +# - _target_: SelectItemsd +# keys: +# - "@image_key" +# - "@label_key" +# - "label_names" + - _target_: CastToTyped + keys: + - "@image_key" + - "@label_key" + dtype: + - "$torch.float32" + - "$torch.uint8" + - _target_: ToTensord + keys: + - "@image_key" + - "@label_key" + preprocessing: + _target_: Compose + transforms: "$@train#deterministic_transforms + @train#random_transforms" + dataset: + _target_: PersistentDataset + data: "@train_datalist" + transform: "@train#preprocessing" + cache_dir: "$@bundle_root + '/cache'" + dataloader: + _target_: DataLoader + dataset: "@train#dataset" + batch_size: 1 + shuffle: true + num_workers: 4 + inferer: + _target_: SimpleInferer + postprocessing: + _target_: Compose + transforms: + - _target_: Activationsd + keys: pred + softmax: true + - _target_: AsDiscreted + keys: + - pred + - label + argmax: + - true + - false + to_onehot: + - "@output_classes" + - "@output_classes" + - _target_: scripts.monai_utils.SplitPredsLabeld + keys: pred +# dice_function: +# _target_: "$engine.state.metrics['train_dice']" + handlers: + - _target_: LrScheduleHandler + lr_scheduler: "@lr_scheduler" + print_lr: true +# step_transform: "@dice_function" + - _target_: ValidationHandler + validator: "@validate#evaluator" + epoch_level: true + interval: "@val_interval" + - _target_: StatsHandler + tag_name: train_loss + output_transform: "$monai.handlers.from_engine(['loss'], first=True)" + - _target_: TensorBoardStatsHandler + log_dir: "@output_dir" + tag_name: train_loss + output_transform: "$monai.handlers.from_engine(['loss'], first=True)" + key_metric: + train_dice: + _target_: MeanDice + output_transform: "$monai.handlers.from_engine(['pred', 'label'])" + include_background: false + additional_metrics: + liver_dice: + _target_: monai.handlers.MeanDice + output_transform: "$monai.handlers.from_engine(['pred_liver', 'label_liver'])" + include_background: false + spleen_dice: + _target_: monai.handlers.MeanDice + output_transform: "$monai.handlers.from_engine(['pred_spleen', 'label_spleen'])" + include_background: false + pancreas_dice: + _target_: monai.handlers.MeanDice + output_transform: "$monai.handlers.from_engine(['pred_pancreas', 'label_pancreas'])" + include_background: false + trainer: + _target_: SupervisedTrainer + device: "@device" + max_epochs: "@max_epochs" + train_data_loader: "@train#dataloader" + network: "@network" + loss_function: "@loss" + # train_interaction: null + optimizer: "@optimizer" + inferer: "@train#inferer" + postprocessing: "@train#postprocessing" + key_train_metric: "@train#key_metric" + additional_metrics: "@train#additional_metrics" + train_handlers: "@train#handlers" + amp: true +validate: + preprocessing: + _target_: Compose + transforms: "%train#deterministic_transforms" + dataset: + # _target_: CacheDataset + # data: "@val_datalist" + # transform: "@validate#preprocessing" + # cache_rate: 0.025 + _target_: PersistentDataset + data: "@val_datalist" + transform: "@validate#preprocessing" + cache_dir: "$@bundle_root + '/cache'" + dataloader: + _target_: DataLoader + dataset: "@validate#dataset" + batch_size: 1 + shuffle: false + num_workers: 4 + inferer: + _target_: SlidingWindowInferer + roi_size: "@spatial_size" + sw_batch_size: 4 + mode: "constant" + overlap: 0.5 + postprocessing: "%train#postprocessing" + handlers: + - _target_: StatsHandler + iteration_log: false + - _target_: TensorBoardStatsHandler + log_dir: "@output_dir" + iteration_log: false + - _target_: CheckpointSaver + save_dir: "@ckpt_dir" + save_dict: + model: "@network" + save_key_metric: true + key_metric_filename: model_latest.pt + key_metric: + val_dice: + _target_: MeanDice + output_transform: "$monai.handlers.from_engine(['pred', 'label'])" + include_background: false + additional_metrics: + val_liver_dice: + _target_: monai.handlers.MeanDice + output_transform: "$monai.handlers.from_engine(['pred_liver', 'label_liver'])" + include_background: false + val_spleen_dice: + _target_: monai.handlers.MeanDice + output_transform: "$monai.handlers.from_engine(['pred_spleen', 'label_spleen'])" + include_background: false + val_pancreas_dice: + _target_: monai.handlers.MeanDice + output_transform: "$monai.handlers.from_engine(['pred_pancreas', 'label_pancreas'])" + include_background: false + evaluator: + _target_: SupervisedEvaluator + device: "@device" + val_data_loader: "@validate#dataloader" + network: "@network" + inferer: "@validate#inferer" + postprocessing: "@validate#postprocessing" + key_val_metric: "@validate#key_metric" + #additional_metrics: "@validate#additional_metrics" + val_handlers: "@validate#handlers" + amp: true +initialize: +- "$monai.utils.set_determinism(seed=123)" +run: +- "$print('Training started... ')" +- "$print('output_channels: ', @output_channels )" +- "$print('spatial_dims: ', @spatial_dims)" +- "$print('Labels dict: ', @labels)" +- "$@train#trainer.run()" \ No newline at end of file diff --git a/models/pediatric_abdominal_ct_segmentation/docs/README.md b/models/pediatric_abdominal_ct_segmentation/docs/README.md new file mode 100644 index 00000000..8d432e13 --- /dev/null +++ b/models/pediatric_abdominal_ct_segmentation/docs/README.md @@ -0,0 +1,145 @@ +# Model Overview +A Pediatric 3D Abdominal Organ Segmentation model, pretrained on adult and pediatric public datasets, and fine tuned for institutional pediatric data. + +Please cite this manuscript: +Somasundaram E, Taylor Z, Alves VV, et al. Deep-Learning Models for Abdominal CT Organ Segmentation in Children: Development and Validation in Internal and Heterogeneous Public Datasets. AJR 2024 May 1 [published online]. Accepted manuscript. doi:10.2214/AJR.24.30931 + +## Data +Modality: +- CT + +Organs Segmented: +- Liver +- Spleen +- Pancreas + +Pre-training data: +- Total Segmentator (815) +- BTCV (30) +- TCIA Pediatric (282) + +Fine-tuning data: +- Cincinnati Children's Liver Spleen CT dataset (275) +- Cincinnati Children's Pancreas CT dataset (146) + +Testing data: +- Cincinnati Children's Liver-Spleen (57) +- Cincinnati Children's Pancreas (35) +- TCIA-Pediatric (74) +- Total Segmentator (50) + +External dataset licenses can be found in accompanying text file. Internal datasets currently not publicly available. + +### Model Architectures +- DynUNet +- SegResNet +- SwinUNETR + +### Hyper-Parameter Tuning +Weights and Biases was used to extensively tune each model for learning rate, scheduler and optimizer. For fine-tuning the fraction of trainable layers was also optimized. DynUNet performed overall better on all test datasets. The Total Segmentator model was also compared and the DynUNet model significantly outperformed Total Segmentator on institutional test data while maintaining relatively stable performance on adult and TCIA datasets. + +### Input +One channel CT image + +### Output +Four channel CT label +- Label 3: pancreas +- Label 2: spleen +- Label 1: liver +- Label 0: background +- 96x96x96 + +## Performance + - MedArxiv to be linked + + +## MONAI Bundle Commands +In addition to the Pythonic APIs, a few command line interfaces (CLI) are provided to interact with the bundle. The CLI supports flexible use cases, such as overriding configs at runtime and predefining arguments in a file. + +For more details usage instructions, visit the [MONAI Bundle Configuration Page](https://docs.monai.io/en/latest/config_syntax.html). + + +#### Execute training: +Dataset used defaults to TotalSegmentator (https://zenodo.org/records/6802614#.ZFPll4TMKUk) +``` +python -m monai.bundle run --config_file configs/train.yaml +``` + +Please note that if the default dataset path is not modified with the actual path in the bundle config files, you can also override it by using `--dataset_dir`: + +``` +python -m monai.bundle run --config_file configs/train.yaml --dataset_dir +``` + +#### `train` config to execute multi-GPU training: + +``` +torchrun --nnodes=1 --nproc_per_node=8 -m monai.bundle run --config_file configs/train-multigpu.yaml +``` + +#### Override the `train` config to execute evaluation with the trained model: + +``` +python -m monai.bundle run --config_file "['configs/train.yaml','configs/evaluate.yaml']" +``` + +#### Execute inference: + +``` +python -m monai.bundle run --config_file configs/inference.yaml +``` + +#### Execute standalone `evaluate`: +``` +python -m monai.bundle run --config_file configs/evaluate.yaml +``` + + +#### Execute standalone `evaluate` in parallel: +``` +torchrun --nnodes=1 --nproc_per_node=8 -m monai.bundle run --config_file configs/evaluate-standalone.yaml +``` + + +#### Export checkpoint for TorchScript: + +``` +python -m monai.bundle ckpt_export network_def --filepath models/dynunet_FT.ts --ckpt_file models/dynunet_FT.pt --meta_file configs/metadata.json --config_file configs/inference.yaml +``` + +#### Export checkpoint to TensorRT based models with fp32 or fp16 precision: + +``` +python -m monai.bundle trt_export --net_id network_def --filepath models/A100/dynunet_FT_trt_16.ts --ckpt_file models/dynunet_FT.pt --meta_file configs/metadata.json --config_file configs/inference.yaml --precision --use_trace "True" --dynamic_batchsize "[1, 4, 8]" --converter_kwargs "{'truncate_long_and_double':True, 'torch_executed_ops': ['aten::upsample_trilinear3d']}" +``` + +#### Execute inference with the TensorRT model: + +``` +python -m monai.bundle run --config_file "['configs/inference.yaml', 'configs/inference_trt.yaml']" +``` + +# References + +[1] Somasundaram E, Taylor Z, Alves VV, et al. Deep-Learning Models for Abdominal CT Organ Segmentation in Children: Development and Validation in Internal and Heterogeneous Public Datasets. AJR 2024 May 1 [published online]. Accepted manuscript. doi:10.2214/AJR.24.30931 + +[2] Wasserthal, J., Breit, H.-C., Meyer, M. T., Pradella, M., Hinck, D., Sauter, A. W., Heye, T., Boll, D., Cyriac, J., Yang, S., Bach, M., & Segeroth, M. (2023, June 16). TotalSegmentator: Robust segmentation of 104 anatomical structures in CT images. arXiv.org. https://arxiv.org/abs/2208.05868 . https://doi.org/10.1148/ryai.230024 + +[3] Jordan, P., Adamson, P. M., Bhattbhatt, V., Beriwal, S., Shen, S., Radermecker, O., Bose, S., Strain, L. S., Offe, M., Fraley, D., Principi, S., Ye, D. H., Wang, A. S., Van Heteren, J., Vo, N.-J., & Schmidt, T. G. (2021). Pediatric Chest/Abdomen/Pelvic CT Exams with Expert Organ Contours (Pediatric-CT-SEG) (Version 2) [Data set]. The Cancer Imaging Archive. https://doi.org/10.7937/TCIA.X0H0-1706 + +[4] https://www.synapse.org/#!Synapse:syn3193805/wiki/89480 + +# License +Copyright (c) MONAI Consortium + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. diff --git a/models/pediatric_abdominal_ct_segmentation/docs/data_license.txt b/models/pediatric_abdominal_ct_segmentation/docs/data_license.txt new file mode 100644 index 00000000..75144fd4 --- /dev/null +++ b/models/pediatric_abdominal_ct_segmentation/docs/data_license.txt @@ -0,0 +1,35 @@ +Third Party Licenses +----------------------------------------------------------------------- + +/*********************************************************************/ +i. The Cancer Imaging Archive Pediatric-CT-SEG + https://www.cancerimagingarchive.net/collection/pediatric-ct-seg/ +/*********************************************************************/ + +Data Usage Agreement / Citations + +Data Citation Required: Users must abide by the TCIA Data Usage Policy and Restrictions. Attribution must include the following citation, including the Digital Object Identifier: + +Jordan, P., Adamson, P. M., Bhattbhatt, V., Beriwal, S., Shen, S., Radermecker, O., Bose, S., Strain, L. S., Offe, M., Fraley, D., Principi, S., Ye, D. H., Wang, A. S., Van Heteren, J., Vo, N.-J., & Schmidt, T. G. (2021). Pediatric Chest/Abdomen/Pelvic CT Exams with Expert Organ Contours (Pediatric-CT-SEG) (Version 2) [Data set]. The Cancer Imaging Archive. https://doi.org/10.7937/TCIA.X0H0-1706 + + +/*********************************************************************/ +ii. Multi-Atlas Labeling Beyond the Cranial Vault - Workshop and Challenge + https://www.synapse.org/#!Synapse:syn3193805/wiki/89480 +/*********************************************************************/ + +Data Usage Agreement / Citations + +Data license may be requested at URL: +https://www.synapse.org/#!Synapse:syn3193805/wiki/217753 + +/*********************************************************************/ +iii. TotalSegmentator v1 + https://zenodo.org/records/6802614#.ZFPll4TMKUk +/*********************************************************************/ + +Data Usage Agreement / Citations + +More details about the dataset can be found in the corresponding paper: https://arxiv.org/abs/2208.05868 (the paper describes v1 of the dataset). Please cite this paper if you use the dataset. + +Wasserthal, J., Breit, H.-C., Meyer, M. T., Pradella, M., Hinck, D., Sauter, A. W., Heye, T., Boll, D., Cyriac, J., Yang, S., Bach, M., & Segeroth, M. (2023, June 16). TotalSegmentator: Robust segmentation of 104 anatomical structures in CT images. arXiv.org. https://arxiv.org/abs/2208.05868 . https://doi.org/10.1148/ryai.230024 \ No newline at end of file diff --git a/models/pediatric_abdominal_ct_segmentation/large_files.yaml b/models/pediatric_abdominal_ct_segmentation/large_files.yaml new file mode 100644 index 00000000..e01e241d --- /dev/null +++ b/models/pediatric_abdominal_ct_segmentation/large_files.yaml @@ -0,0 +1,7 @@ +large_files: + - path: "models/dynunet_FT.pt" + url: "https://drive.google.com/file/d/1bR9iQjc5Qo_yD-XN6ZYf3eiXhX9SEIyM/view?usp=sharing" + hash_val: "" + hash_type: "" + - path: "models/A100/dynunet_FT_trt_16.ts" + url: "https://drive.google.com/file/d/1k44aoW-bXAuG5i9t2nw1qWTidcDsfelQ/view?usp=sharing" \ No newline at end of file diff --git a/models/pediatric_abdominal_ct_segmentation/scripts/__init__.py b/models/pediatric_abdominal_ct_segmentation/scripts/__init__.py new file mode 100644 index 00000000..ed55d352 --- /dev/null +++ b/models/pediatric_abdominal_ct_segmentation/scripts/__init__.py @@ -0,0 +1 @@ +import scripts.utils diff --git a/models/pediatric_abdominal_ct_segmentation/scripts/compute_metric.py b/models/pediatric_abdominal_ct_segmentation/scripts/compute_metric.py new file mode 100644 index 00000000..1c7bb1c2 --- /dev/null +++ b/models/pediatric_abdominal_ct_segmentation/scripts/compute_metric.py @@ -0,0 +1,162 @@ +# Copyright (c) MONAI Consortium +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# http://www.apache.org/licenses/LICENSE-2.0 +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +""" +This example shows how to efficiently compute Dice scores for pairs of segmentation prediction +and references in multi-processing based on MONAI's metrics API. +It can even run on multi-nodes. +Main steps to set up the distributed data parallel: + +- Execute `torchrun` to create processes on every node for every process. + It receives parameters as below: + `--nproc_per_node=NUM_PROCESSES_PER_NODE` + `--nnodes=NUM_NODES` + For more details, refer to https://github.com/pytorch/pytorch/blob/master/torch/distributed/run.py. + Alternatively, we can also use `torch.multiprocessing.spawn` to start program, but it that case, need to handle + all the above parameters and compute `rank` manually, then set to `init_process_group`, etc. + `torchrun` is even more efficient than `torch.multiprocessing.spawn`. +- Use `init_process_group` to initialize every process. +- Partition the saved predictions and labels into ranks for parallel computation. +- Compute `Dice Metric` on every process, reduce the results after synchronization. + +Note: + `torchrun` will launch `nnodes * nproc_per_node = world_size` processes in total. + Example script to execute this program on a single node with 2 processes: + `torchrun --nproc_per_node=2 compute_metric.py` + +Referring to: https://pytorch.org/tutorials/intermediate/ddp_tutorial.html + +""" + +import argparse +import os +from glob import glob + +import nibabel as nib +import numpy as np +import torch +import torch.distributed as dist +from monai.data import create_test_image_3d, partition_dataset +from monai.handlers import write_metrics_reports +from monai.metrics import DiceMetric +from monai.transforms import ( + AsDiscreted, + Compose, + EnsureChannelFirstd, + KeepLargestConnectedComponentd, + LoadImaged, + Orientationd, + ScaleIntensityd, + ToDeviced, + ToTensord, +) +from monai.utils import string_list_all_gather + +from scripts.monai_utils import CopyFilenamesd + + +def compute(datalist, output_dir): + # generate synthetic data for the example + local_rank = int(os.environ["LOCAL_RANK"]) + # initialize the distributed evaluation process, change to gloo backend if computing on CPU + dist.init_process_group(backend="nccl", init_method="env://") + + # split data for every subprocess, for example, 16 processes compute in parallel + data_part = partition_dataset( + data=datalist, + num_partitions=dist.get_world_size(), + shuffle=False, + even_divisible=False, + )[dist.get_rank()] + + device = torch.device(f"cuda:{local_rank}") + torch.cuda.set_device(device) + # define transforms for predictions and labels + # labels = {'background': 0, 'liver': 1, 'spleen': 2, 'pancreas': 3} + transforms = Compose( + [ + CopyFilenamesd(keys="label"), + LoadImaged(keys=["pred", "label"]), + ToDeviced(keys=["pred", "label"], device=device), + EnsureChannelFirstd(keys=["pred", "label"]), + Orientationd(keys=("pred", "label"), axcodes="RAS"), + AsDiscreted( + keys=("pred", "label"), argmax=(False, False), to_onehot=(4, 4) + ), + ] + ) + + data_part = [transforms(item) for item in data_part] + + # compute metrics for current process + metric = DiceMetric(include_background=False, reduction="mean", get_not_nans=False) + metric(y_pred=[i["pred"] for i in data_part], y=[i["label"] for i in data_part]) + filenames = [item["filename"] for item in data_part] + # all-gather results from all the processes and reduce for final result + result = metric.aggregate().item() + filenames = string_list_all_gather(strings=filenames) + + if local_rank == 0: + print("mean dice: ", result) + # generate metrics reports at: output/mean_dice_raw.csv, output/mean_dice_summary.csv, output/metrics.csv + write_metrics_reports( + save_dir=output_dir, + images=filenames, + metrics={"mean_dice": result}, + metric_details={"mean_dice": metric.get_buffer()}, + summary_ops="*", + ) + + metric.reset() + + dist.destroy_process_group() + + +def compute_single_node(datalist, output_dir): + filenames = [d["label"].split("/")[-1] for d in datalist] + + data_part = datalist + device = torch.device(f"cuda:{local_rank}") + torch.cuda.set_device(device) + + # define transforms for predictions and labels + labels = {"background": 0, "liver": 1, "spleen": 2, "pancreas": 3} + transforms = Compose( + [ + LoadImaged(keys=["pred", "label"]), + ToDeviced(keys=["pred", "label"], device=device), + EnsureChannelFirstd(keys=["pred", "label"]), + Orientationd(keys=("pred", "label"), axcodes="RAS"), + AddLabelNamesd(keys=("pred", "label"), label_names=labels), + AsDiscreted( + keys=("pred", "label"), argmax=(False, False), to_onehot=(4, 4) + ), + ] + ) + data_part = [transforms(item) for item in data_part] + # compute metrics for current process + metric = DiceMetric(include_background=False, reduction="mean", get_not_nans=False) + for d in datalist: + d = transforms(d) + metric(y_pred=[d["pred"]], y=[d["label"]]) + + result = metric.aggregate().item() + + print("mean dice: ", result) + write_metrics_reports( + save_dir=output_dir, + images=filenames, + metrics={"mean_dice": result}, + metric_details={"mean_dice": metric.get_buffer()}, + summary_ops="*", + ) + + metric.reset() diff --git a/models/pediatric_abdominal_ct_segmentation/scripts/lr_scheduler.py b/models/pediatric_abdominal_ct_segmentation/scripts/lr_scheduler.py new file mode 100644 index 00000000..4a93080d --- /dev/null +++ b/models/pediatric_abdominal_ct_segmentation/scripts/lr_scheduler.py @@ -0,0 +1,211 @@ +# Copyright 2020 - 2021 MONAI Consortium +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# http://www.apache.org/licenses/LICENSE-2.0 +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import math +import warnings +from typing import List + +from torch import nn as nn +from torch.optim import Adam, Optimizer +from torch.optim.lr_scheduler import LambdaLR, _LRScheduler + +__all__ = ["LinearLR", "ExponentialLR"] + + +class _LRSchedulerMONAI(_LRScheduler): + """Base class for increasing the learning rate between two boundaries over a number + of iterations""" + + def __init__( + self, optimizer: Optimizer, end_lr: float, num_iter: int, last_epoch: int = -1 + ) -> None: + """ + Args: + optimizer: wrapped optimizer. + end_lr: the final learning rate. + num_iter: the number of iterations over which the test occurs. + last_epoch: the index of last epoch. + Returns: + None + """ + self.end_lr = end_lr + self.num_iter = num_iter + super(_LRSchedulerMONAI, self).__init__(optimizer, last_epoch) + + +class LinearLR(_LRSchedulerMONAI): + """Linearly increases the learning rate between two boundaries over a number of + iterations. + """ + + def get_lr(self): + r = self.last_epoch / (self.num_iter - 1) + return [base_lr + r * (self.end_lr - base_lr) for base_lr in self.base_lrs] + + +class ExponentialLR(_LRSchedulerMONAI): + """Exponentially increases the learning rate between two boundaries over a number of + iterations. + """ + + def get_lr(self): + r = self.last_epoch / (self.num_iter - 1) + return [base_lr * (self.end_lr / base_lr) ** r for base_lr in self.base_lrs] + + +class WarmupCosineSchedule(LambdaLR): + """Linear warmup and then cosine decay. + Based on https://huggingface.co/ implementation. + """ + + def __init__( + self, + optimizer: Optimizer, + warmup_steps: int, + t_total: int, + cycles: float = 0.5, + last_epoch: int = -1, + ) -> None: + """ + Args: + optimizer: wrapped optimizer. + warmup_steps: number of warmup iterations. + t_total: total number of training iterations. + cycles: cosine cycles parameter. + last_epoch: the index of last epoch. + Returns: + None + """ + self.warmup_steps = warmup_steps + self.t_total = t_total + self.cycles = cycles + super(WarmupCosineSchedule, self).__init__( + optimizer, self.lr_lambda, last_epoch + ) + + def lr_lambda(self, step): + if step < self.warmup_steps: + return float(step) / float(max(1.0, self.warmup_steps)) + progress = float(step - self.warmup_steps) / float( + max(1, self.t_total - self.warmup_steps) + ) + return max( + 0.0, 0.5 * (1.0 + math.cos(math.pi * float(self.cycles) * 2.0 * progress)) + ) + + +class LinearWarmupCosineAnnealingLR(_LRScheduler): + def __init__( + self, + optimizer: Optimizer, + warmup_epochs: int, + max_epochs: int, + warmup_start_lr: float = 0.0, + eta_min: float = 0.0, + last_epoch: int = -1, + ) -> None: + """ + Args: + optimizer (Optimizer): Wrapped optimizer. + warmup_epochs (int): Maximum number of iterations for linear warmup + max_epochs (int): Maximum number of iterations + warmup_start_lr (float): Learning rate to start the linear warmup. Default: 0. + eta_min (float): Minimum learning rate. Default: 0. + last_epoch (int): The index of last epoch. Default: -1. + """ + self.warmup_epochs = warmup_epochs + self.max_epochs = max_epochs + self.warmup_start_lr = warmup_start_lr + self.eta_min = eta_min + + super(LinearWarmupCosineAnnealingLR, self).__init__(optimizer, last_epoch) + + def get_lr(self) -> List[float]: + """ + Compute learning rate using chainable form of the scheduler + """ + if not self._get_lr_called_within_step: + warnings.warn( + "To get the last learning rate computed by the scheduler, " + "please use `get_last_lr()`.", + UserWarning, + ) + + if self.last_epoch == 0: + return [self.warmup_start_lr] * len(self.base_lrs) + elif self.last_epoch < self.warmup_epochs: + return [ + group["lr"] + + (base_lr - self.warmup_start_lr) / (self.warmup_epochs - 1) + for base_lr, group in zip(self.base_lrs, self.optimizer.param_groups) + ] + elif self.last_epoch == self.warmup_epochs: + return self.base_lrs + elif (self.last_epoch - 1 - self.max_epochs) % ( + 2 * (self.max_epochs - self.warmup_epochs) + ) == 0: + return [ + group["lr"] + + (base_lr - self.eta_min) + * (1 - math.cos(math.pi / (self.max_epochs - self.warmup_epochs))) + / 2 + for base_lr, group in zip(self.base_lrs, self.optimizer.param_groups) + ] + + return [ + ( + 1 + + math.cos( + math.pi + * (self.last_epoch - self.warmup_epochs) + / (self.max_epochs - self.warmup_epochs) + ) + ) + / ( + 1 + + math.cos( + math.pi + * (self.last_epoch - self.warmup_epochs - 1) + / (self.max_epochs - self.warmup_epochs) + ) + ) + * (group["lr"] - self.eta_min) + + self.eta_min + for group in self.optimizer.param_groups + ] + + def _get_closed_form_lr(self) -> List[float]: + """ + Called when epoch is passed as a param to the `step` function of the scheduler. + """ + if self.last_epoch < self.warmup_epochs: + return [ + self.warmup_start_lr + + self.last_epoch + * (base_lr - self.warmup_start_lr) + / (self.warmup_epochs - 1) + for base_lr in self.base_lrs + ] + + return [ + self.eta_min + + 0.5 + * (base_lr - self.eta_min) + * ( + 1 + + math.cos( + math.pi + * (self.last_epoch - self.warmup_epochs) + / (self.max_epochs - self.warmup_epochs) + ) + ) + for base_lr in self.base_lrs + ] diff --git a/models/pediatric_abdominal_ct_segmentation/scripts/monai_utils.py b/models/pediatric_abdominal_ct_segmentation/scripts/monai_utils.py new file mode 100644 index 00000000..0226abc5 --- /dev/null +++ b/models/pediatric_abdominal_ct_segmentation/scripts/monai_utils.py @@ -0,0 +1,231 @@ +from __future__ import annotations + +import json +import logging +import random +import warnings +from collections.abc import Hashable, Mapping, Sequence, Sized + +import numpy as np +import torch +from monai.config import KeysCollection +from monai.data import MetaTensor +from monai.networks.layers import GaussianFilter +from monai.transforms.transform import MapTransform, Randomizable, Transform +from monai.utils import min_version, optional_import + +# measure, _ = optional_import("skimage.measure", "0.14.2", min_version) + +logger = logging.getLogger(__name__) + +# distance_transform_cdt, _ = optional_import("scipy.ndimage.morphology", name="distance_transform_cdt") + +import os +from typing import TYPE_CHECKING, Any, Callable, Iterable, Sequence + +import torch.nn +from monai.engines import SupervisedTrainer +from monai.engines.utils import get_devices_spec +from torch.nn.parallel import DataParallel, DistributedDataParallel +from torch.optim.optimizer import Optimizer + + +def get_device_list(n_gpu): + if type(n_gpu) is not list: + n_gpu = [n_gpu] + device_list = get_devices_spec(n_gpu) + if torch.cuda.device_count() >= max(n_gpu): + device_list = [d for d in device_list if d in n_gpu] + else: + logging.info( + "Highest GPU ID provided in 'n_gpu' is larger than number of GPUs available, assigning GPUs starting from 0 to match n_gpu length of {}".format( + len(n_gpu) + ) + ) + device_list = [d for d in device_list][: len(n_gpu)] + return device_list + + +def SupervisedTrainer_multi_gpu( + max_epochs: int, + train_data_loader, + network: torch.nn.Module, + optimizer: Optimizer, + loss_function: Callable, + device: Sequence[str | torch.device] | None = None, + epoch_length: int | None = None, + non_blocking: bool = False, + iteration_update: Callable[[Engine, Any], Any] | None = None, + inferer: Inferer | None = None, + postprocessing: Transform | None = None, + key_train_metric: dict[str, Metric] | None = None, + additional_metrics: dict[str, Metric] | None = None, + train_handlers: Sequence | None = None, + amp: bool = False, + distributed: bool = False, +): + devices_ = device + if not device: + devices_ = get_devices_spec(device) # Using all devices i.e GPUs + + # if device: + # if next(network.parameters()).device.index != device[0]: + # network.to(devices_[0]) + # else: + # if next(network.parameters()).device.index != devices_[0].index: + # network.to(devices_[0]) + # + net = network + if distributed: + if len(devices_) > 1: + raise ValueError( + f"for distributed training, `devices` must contain only 1 GPU or CPU, but got {devices_}." + ) + net = DistributedDataParallel(net, device_ids=devices_) + elif len(devices_) > 1: + net = DataParallel(net, device_ids=devices_) # ,output_device=devices_[0]) + + return SupervisedTrainer( + device=devices_[0], + network=net, + optimizer=optimizer, + loss_function=loss_function, + max_epochs=max_epochs, + train_data_loader=train_data_loader, + epoch_length=epoch_length, + non_blocking=non_blocking, + iteration_update=iteration_update, + inferer=inferer, + postprocessing=postprocessing, + key_train_metric=key_train_metric, + additional_metrics=additional_metrics, + train_handlers=train_handlers, + amp=amp, + ) + + +class SupervisedTrainer_m_gpu(SupervisedTrainer): + def __init__( + self, + max_epochs: int, + train_data_loader, + network: torch.nn.Module, + optimizer: Optimizer, + loss_function: Callable, + device: Sequence[str | torch.device] | None = None, + epoch_length: int | None = None, + non_blocking: bool = False, + iteration_update: Callable[[Engine, Any], Any] | None = None, + inferer: Inferer | None = None, + postprocessing: Transform | None = None, + key_train_metric: dict[str, Metric] | None = None, + additional_metrics: dict[str, Metric] | None = None, + train_handlers: Sequence | None = None, + amp: bool = False, + distributed: bool = False, + ): + self.devices_ = device + if not device: + self.devices_ = get_devices_spec(device) # Using all devices i.e GPUs + + # if device: + # if next(network.parameters()).device.index != device[0]: + # network.to(devices_[0]) + # else: + # if next(network.parameters()).device.index != devices_[0].index: + # network.to(devices_[0]) + # + self.net = network + if distributed: + if len(self.devices_) > 1: + raise ValueError( + f"for distributed training, `devices` must contain only 1 GPU or CPU, but got {self.devices_}." + ) + self.net = DistributedDataParallel(self.net, device_ids=self.devices_) + elif len(self.devices_) > 1: + self.net = DataParallel( + self.net, device_ids=self.devices_ + ) # ,output_device=devices_[0]) + + super().__init__( + device=self.devices_[0], + network=self.net, + optimizer=optimizer, + loss_function=loss_function, + max_epochs=max_epochs, + train_data_loader=train_data_loader, + epoch_length=epoch_length, + non_blocking=non_blocking, + iteration_update=iteration_update, + inferer=inferer, + postprocessing=postprocessing, + key_train_metric=key_train_metric, + additional_metrics=additional_metrics, + train_handlers=train_handlers, + amp=amp, + ) + + +class AddLabelNamesd(MapTransform): + def __init__( + self, + keys: KeysCollection, + label_names: dict[str, int] | None = None, + allow_missing_keys: bool = False, + ): + """ + Normalize label values according to label names dictionary + + Args: + keys: The ``keys`` parameter will be used to get and set the actual data item to transform + label_names: all label names + """ + super().__init__(keys, allow_missing_keys) + + self.label_names = label_names or {} + + def __call__( + self, data: Mapping[Hashable, np.ndarray] + ) -> dict[Hashable, np.ndarray]: + d: dict = dict(data) + d["label_names"] = self.label_names + return d + + +class CopyFilenamesd(MapTransform): + def __init__(self, keys: KeysCollection, allow_missing_keys: bool = False): + """ + Copy Filenames for future use + + Args: + keys: The ``keys`` parameter will be used to get and set the actual data item to transform + """ + super().__init__(keys, allow_missing_keys) + + def __call__( + self, data: Mapping[Hashable, np.ndarray] + ) -> dict[Hashable, np.ndarray]: + d: dict = dict(data) + d["filename"] = os.path.basename(d["label"]) + return d + + +class SplitPredsLabeld(MapTransform): + """ + Split preds and labels for individual evaluation + + """ + + def __call__( + self, data: Mapping[Hashable, np.ndarray] + ) -> dict[Hashable, np.ndarray]: + d: dict = dict(data) + for key in self.key_iterator(d): + if key == "pred": + for idx, (key_label, _) in enumerate(d["label_names"].items()): + if key_label != "background": + d[f"pred_{key_label}"] = d[key][idx, ...][None] + d[f"label_{key_label}"] = d["label"][idx, ...][None] + elif key != "pred": + logger.info("This is only for pred key") + return d diff --git a/models/pediatric_abdominal_ct_segmentation/scripts/prepare_datalist_monailabel.py b/models/pediatric_abdominal_ct_segmentation/scripts/prepare_datalist_monailabel.py new file mode 100644 index 00000000..0e4bdfe9 --- /dev/null +++ b/models/pediatric_abdominal_ct_segmentation/scripts/prepare_datalist_monailabel.py @@ -0,0 +1,177 @@ +import argparse +import glob +import json +import logging +import os +import sys + +import monai +from sklearn.model_selection import train_test_split + + +def produce_datalist_splits( + datalist, + splits: list = ["test"], + train_split: float = 0.80, + valid_test_split: float = 0.50, +): + """ + This function is used to split the dataset. + It will produce "train_size" number of samples for training. + """ + if "train" in splits: + train_list, other_list = train_test_split(datalist, train_size=train_split) + if "valid" in splits: + val_list, test_list = train_test_split( + other_list, train_size=valid_test_split + ) + return { + "training": train_list, + "validation": val_list, + "testing": test_list, + } + else: + return {"training": train_list, "testing": other_list} + elif "valid" in splits: + val_list, test_list = train_test_split(datalist, train_size=valid_test_split) + return {"validation": val_list, "testing": test_list} + else: + return {"testing": datalist} + + +def keep_image_label_pairs_only(a_images, a_labels, i_folder, l_folder): + image_names = [a.split("/")[-1] for a in a_images] + label_names = [a.split("/")[-1] for a in a_labels] + # Check if all_labels == all_images, if all_images < all_labels, truncate all_labels + image_set = set(image_names) + label_set = set(label_names) + labelmissing = image_set.difference(label_set) + # Find names labels not in images + imagemissing = label_set.difference(image_set) + # print('Data_path: ', a_images[0]) + # print('Data folder: ',a_images[0].split('/')[-2]) + # print('Labels missing for: ', len(labelmissing)) + # print('Images missing for: ', len(imagemissing)) + a_images = sorted( + [os.path.join(i_folder, a) for a in image_names if a in label_names] + ) + ## Keep only labels that have a scan + image_names = [a.split("/")[-1] for a in a_images] + a_labels = sorted( + [os.path.join(l_folder, a) for a in label_names if a in image_names] + ) + return a_images, a_labels + + +def parse_files(images_folder, labels_folder, file_extension_pattern): + logging.info( + f"parsing files at: {os.path.join(images_folder, file_extension_pattern)}" + ) + all_images = sorted(glob.glob(os.path.join(images_folder, file_extension_pattern))) + all_labels = sorted(glob.glob(os.path.join(labels_folder, file_extension_pattern))) + return all_images, all_labels + + +def get_datalist(args, images_folder, labels_folder): + file_extension_pattern = "*" + args.file_extension + "*" + if type(images_folder) is list: + all_images = [] + all_labels = [] + for ifolder, lfolder in zip(images_folder, labels_folder): + a_images, a_labels = parse_files(ifolder, lfolder, file_extension_pattern) + a_images, a_labels = keep_image_label_pairs_only( + a_images, a_labels, ifolder, lfolder + ) + all_images += a_images + all_labels += a_labels + else: + all_images, all_labels = parse_files( + images_folder, labels_folder, file_extension_pattern + ) + all_images, all_labels = keep_image_label_pairs_only( + all_images, all_labels, images_folder, labels_folder + ) + + logging.info("Length of all_images: {}".format(len(all_images))) + logging.info("Length of all_labels: {}".format(len(all_labels))) + + datalist = [ + {"image": image_name, "label": label_name} + for image_name, label_name in zip(all_images, all_labels) + ] + + # datalist = datalist[0 : args.limit] if args.limit else datalist + logging.info(f"datalist length is {len(datalist)}") + return datalist + + +def main(args): + """ + split the dataset and output the data list into a json file. + """ + data_file_base_dir = args.path + output_json = args.output + # produce deterministic data splits + monai.utils.set_determinism(seed=123) + datalist = get_datalist( + args, data_file_base_dir, os.path.join(data_file_base_dir, args.labels_folder) + ) + datalist = produce_datalist_splits( + datalist, args.splits, args.train_split, args.valid_test_split + ) + with open(output_json, "w") as f: + json.dump(datalist, f, ensure_ascii=True, indent=4) + logging.info("datalist json file saved to: {}".format(output_json)) + + +if __name__ == "__main__": + logging.basicConfig( + stream=sys.stdout, + level=logging.DEBUG, + format="[%(asctime)s.%(msecs)03d][%(levelname)5s](%(name)s) - %(message)s", + datefmt="%Y-%m-%d %H:%M:%S", + ) + parser = argparse.ArgumentParser(description="") + parser.add_argument( + "--path", + type=str, + default="/workspace/data/msd/Task07_Pancreas", + help="root path of MSD Task07_Pancreas dataset.", + ) + parser.add_argument( + "--output", + type=str, + default="dataset_0.json", + help="relative path of output datalist json file.", + ) + parser.add_argument( + "--train_split", type=int, default=0.80, help="fraction of Training samples." + ) + parser.add_argument( + "--valid_test_split", + type=int, + default=0.50, + help="fraction of valid/test samples.", + ) + parser.add_argument( + "--splits", + type=list, + default=["test"], + help="splits to use for train, valid, and test.", + ) + parser.add_argument( + "--file_extension", + type=str, + default="nii", + help="file extension of images and labels.", + ) + parser.add_argument( + "--labels_folder", + type=str, + default="labels/final", + help="labels sub folder name", + ) + + args = parser.parse_args() + + main(args) diff --git a/models/pediatric_abdominal_ct_segmentation/scripts/utils.py b/models/pediatric_abdominal_ct_segmentation/scripts/utils.py new file mode 100644 index 00000000..70257a04 --- /dev/null +++ b/models/pediatric_abdominal_ct_segmentation/scripts/utils.py @@ -0,0 +1,2 @@ +def test(): + print("Test Function PRINTS") From 55aa1462aa0ae17e7204386d1661eb3ab5721835 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Fri, 31 May 2024 22:19:44 +0000 Subject: [PATCH 02/10] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- .../configs/TS_test.json | 2 +- .../configs/evaluate-standalone-parallel.yaml | 4 ++-- .../configs/evaluate-standalone.yaml | 4 ++-- .../configs/inference.yaml | 10 +++++----- .../configs/inference_segresnet.yaml | 6 +++--- .../configs/inference_swinunetr.yaml | 6 +++--- .../configs/logging.conf | 2 +- .../configs/metadata.json | 4 ++-- .../configs/train-multigpu.yaml | 18 +++++++++--------- .../configs/train.yaml | 16 ++++++++-------- .../docs/README.md | 8 ++++---- .../docs/data_license.txt | 2 +- .../large_files.yaml | 2 +- 13 files changed, 42 insertions(+), 42 deletions(-) diff --git a/models/pediatric_abdominal_ct_segmentation/configs/TS_test.json b/models/pediatric_abdominal_ct_segmentation/configs/TS_test.json index f8f5d807..6088acfc 100644 --- a/models/pediatric_abdominal_ct_segmentation/configs/TS_test.json +++ b/models/pediatric_abdominal_ct_segmentation/configs/TS_test.json @@ -403,4 +403,4 @@ "label": "/processed/Public/CT_TotalSegmentator/TS_split/test/s1386.nii.gz" } ] -} \ No newline at end of file +} diff --git a/models/pediatric_abdominal_ct_segmentation/configs/evaluate-standalone-parallel.yaml b/models/pediatric_abdominal_ct_segmentation/configs/evaluate-standalone-parallel.yaml index be5f3a49..2e0af963 100644 --- a/models/pediatric_abdominal_ct_segmentation/configs/evaluate-standalone-parallel.yaml +++ b/models/pediatric_abdominal_ct_segmentation/configs/evaluate-standalone-parallel.yaml @@ -9,10 +9,10 @@ workflow_type: evaluate spatial_dims: "$len(@spatial_size)" bundle_root: "." output_dir: "$@bundle_root + '/eval/dynunet_FT_trt_32'" -dataset_dir: "/processed/Public/CT_TotalSegmentator/TS_split/test/" +dataset_dir: "/processed/Public/CT_TotalSegmentator/TS_split/test/" data_list_file_path: "$@bundle_root + '/configs/TS_test.json'" datalist: "$monai.data.load_decathlon_datalist(@data_list_file_path, data_list_key='validation')" datalist_pred: "$[{**d, 'pred': os.path.join(@output_dir, d['label'].split('/')[-1].split('.')[0] + '_trans.nii.gz')} for d in @datalist]" run: #- "$compute_abdominal_ct_metrics(@datalist_pred, @output_dir)" -- "$compute(@datalist_pred, @output_dir)" \ No newline at end of file +- "$compute(@datalist_pred, @output_dir)" diff --git a/models/pediatric_abdominal_ct_segmentation/configs/evaluate-standalone.yaml b/models/pediatric_abdominal_ct_segmentation/configs/evaluate-standalone.yaml index 99201ec3..229d0207 100644 --- a/models/pediatric_abdominal_ct_segmentation/configs/evaluate-standalone.yaml +++ b/models/pediatric_abdominal_ct_segmentation/configs/evaluate-standalone.yaml @@ -9,9 +9,9 @@ workflow_type: evaluate spatial_dims: "$len(@spatial_size)" bundle_root: "." output_dir: "$@bundle_root + '/eval/dynunet_FT_trt_32'" -dataset_dir: "/processed/Public/CT_TotalSegmentator/TS_split/test/" +dataset_dir: "/processed/Public/CT_TotalSegmentator/TS_split/test/" data_list_file_path: "$@bundle_root + '/configs/TS_test.json'" datalist: "$monai.data.load_decathlon_datalist(@data_list_file_path, data_list_key='validation')" datalist_pred: "$[{**d, 'pred': os.path.join(@output_dir, d['label'].split('/')[-1].split('.')[0] + '_trans.nii.gz')} for d in @datalist]" run: -- "$compute_single_node(@datalist_pred, @output_dir)" \ No newline at end of file +- "$compute_single_node(@datalist_pred, @output_dir)" diff --git a/models/pediatric_abdominal_ct_segmentation/configs/inference.yaml b/models/pediatric_abdominal_ct_segmentation/configs/inference.yaml index c7ad44a7..f0605bc7 100644 --- a/models/pediatric_abdominal_ct_segmentation/configs/inference.yaml +++ b/models/pediatric_abdominal_ct_segmentation/configs/inference.yaml @@ -11,11 +11,11 @@ output_channels: 4 # arch_ckpt: "$torch.load(@arch_ckpt_path, map_location=torch.device('cuda'))" bundle_root: "." output_dir: "$@bundle_root + '/eval/dynunet_FT'" -dataset_dir: "/processed/Public/CT_TotalSegmentator/TS_split/test/" +dataset_dir: "/processed/Public/CT_TotalSegmentator/TS_split/test/" data_list_file_path: "$@bundle_root + '/configs/TS_test.json'" datalist: "$monai.data.load_decathlon_datalist(@data_list_file_path, data_list_key='validation')" device: "$torch.device('cuda:0' if torch.cuda.is_available() else 'cpu')" -spatial_size: +spatial_size: - 96 - 96 - 96 @@ -43,7 +43,7 @@ network_def: - 2 - 2 - 2 - - + - - 2 - 2 - 1 @@ -56,7 +56,7 @@ network_def: - 2 - 2 - 1 - norm_name: "instance" + norm_name: "instance" deep_supervision: false res_block: true network: "$@network_def.to(@device)" @@ -158,4 +158,4 @@ evaluator: initialize: - "$setattr(torch.backends.cudnn, 'benchmark', True)" run: -- "$@evaluator.run()" \ No newline at end of file +- "$@evaluator.run()" diff --git a/models/pediatric_abdominal_ct_segmentation/configs/inference_segresnet.yaml b/models/pediatric_abdominal_ct_segmentation/configs/inference_segresnet.yaml index 8d09fbc4..11f952f5 100644 --- a/models/pediatric_abdominal_ct_segmentation/configs/inference_segresnet.yaml +++ b/models/pediatric_abdominal_ct_segmentation/configs/inference_segresnet.yaml @@ -9,11 +9,11 @@ output_classes: 4 output_channels: 4 bundle_root: "." output_dir: "$@bundle_root + '/eval/segresnet_FT'" -dataset_dir: "/processed/Public/CT_TotalSegmentator/TS_split/test/" +dataset_dir: "/processed/Public/CT_TotalSegmentator/TS_split/test/" data_list_file_path: "$@bundle_root + '/configs/TS_test.json'" datalist: "$monai.data.load_decathlon_datalist(@data_list_file_path, data_list_key='validation')" device: "$torch.device('cuda:0' if torch.cuda.is_available() else 'cpu')" -spatial_size: +spatial_size: - 96 - 96 - 96 @@ -137,4 +137,4 @@ evaluator: initialize: - "$setattr(torch.backends.cudnn, 'benchmark', True)" run: -- "$@evaluator.run()" \ No newline at end of file +- "$@evaluator.run()" diff --git a/models/pediatric_abdominal_ct_segmentation/configs/inference_swinunetr.yaml b/models/pediatric_abdominal_ct_segmentation/configs/inference_swinunetr.yaml index 5ba2ce9f..3847fb6c 100644 --- a/models/pediatric_abdominal_ct_segmentation/configs/inference_swinunetr.yaml +++ b/models/pediatric_abdominal_ct_segmentation/configs/inference_swinunetr.yaml @@ -11,11 +11,11 @@ output_channels: 4 # arch_ckpt: "$torch.load(@arch_ckpt_path, map_location=torch.device('cuda'))" bundle_root: "." output_dir: "$@bundle_root + '/eval/swinunetr_FT'" -dataset_dir: "/processed/Public/CT_TotalSegmentator/TS_split/test/" +dataset_dir: "/processed/Public/CT_TotalSegmentator/TS_split/test/" data_list_file_path: "$@bundle_root + '/configs/TS_test.json'" datalist: "$monai.data.load_decathlon_datalist(@data_list_file_path, data_list_key='validation')" device: "$torch.device('cuda:0' if torch.cuda.is_available() else 'cpu')" -spatial_size: +spatial_size: - 96 - 96 - 96 @@ -134,4 +134,4 @@ evaluator: initialize: - "$setattr(torch.backends.cudnn, 'benchmark', True)" run: -- "$@evaluator.run()" \ No newline at end of file +- "$@evaluator.run()" diff --git a/models/pediatric_abdominal_ct_segmentation/configs/logging.conf b/models/pediatric_abdominal_ct_segmentation/configs/logging.conf index db85a0b9..91c1a21c 100644 --- a/models/pediatric_abdominal_ct_segmentation/configs/logging.conf +++ b/models/pediatric_abdominal_ct_segmentation/configs/logging.conf @@ -18,4 +18,4 @@ formatter=fullFormatter args=(sys.stdout,) [formatter_fullFormatter] -format=%(asctime)s - %(name)s - %(levelname)s - %(message)s \ No newline at end of file +format=%(asctime)s - %(name)s - %(levelname)s - %(message)s diff --git a/models/pediatric_abdominal_ct_segmentation/configs/metadata.json b/models/pediatric_abdominal_ct_segmentation/configs/metadata.json index 24fa85a1..b6afbc9f 100644 --- a/models/pediatric_abdominal_ct_segmentation/configs/metadata.json +++ b/models/pediatric_abdominal_ct_segmentation/configs/metadata.json @@ -40,7 +40,7 @@ "label_classes": "single channel data, 1 is liver, 2 is spleen, 3 is pancreas and 0 is everything else", "pred_classes": "single channel data, 1 is liver, 2 is spleen, 3 is pancreas and 0 is everything else", "eval_metrics": { - "TS_mean_dice": 0.90, + "TS_mean_dice": 0.9, "TCIA_mean_dice": 0.87, "CCHMC_mean_dice": 0.89 }, @@ -98,4 +98,4 @@ } } } -} \ No newline at end of file +} diff --git a/models/pediatric_abdominal_ct_segmentation/configs/train-multigpu.yaml b/models/pediatric_abdominal_ct_segmentation/configs/train-multigpu.yaml index 1d094b4b..2559b812 100644 --- a/models/pediatric_abdominal_ct_segmentation/configs/train-multigpu.yaml +++ b/models/pediatric_abdominal_ct_segmentation/configs/train-multigpu.yaml @@ -24,12 +24,12 @@ dataset_dir: "/processed/Public/CT_TotalSegmentator/TS_split/test/" #"/workspac data_list_file_path: "$@bundle_root + '/configs/TS_test.json'" train_datalist: "$monai.data.load_decathlon_datalist(@data_list_file_path, data_list_key='training')" val_datalist: "$monai.data.load_decathlon_datalist(@data_list_file_path, data_list_key='validation')" -n_gpu: +n_gpu: - 0 - 1 device: "$torch.device('cuda:' + str(@n_gpu[0]) if torch.cuda.is_available() else 'cpu')" device_list: "$scripts.monai_utils.get_device_list(@n_gpu)" -spatial_size: +spatial_size: - 96 - 96 - 96 @@ -57,7 +57,7 @@ network_def: - 2 - 2 - 2 - - + - - 2 - 2 - 1 @@ -70,7 +70,7 @@ network_def: - 2 - 2 - 1 - norm_name: "instance" + norm_name: "instance" deep_supervision: false res_block: true network: "$@network_def.to(@device)" @@ -236,7 +236,7 @@ train: argmax: - true - false - to_onehot: + to_onehot: - "@output_classes" - "@output_classes" - _target_: scripts.monai_utils.SplitPredsLabeld # monai.apps.deepedit.transforms @@ -278,8 +278,8 @@ train: output_transform: "$monai.handlers.from_engine(['pred_pancreas', 'label_pancreas'])" include_background: false trainer: - _target_: scripts.monai_utils.SupervisedTrainer_m_gpu # SupervisedTrainer - device: "@device_list" # "@device" + _target_: scripts.monai_utils.SupervisedTrainer_m_gpu # SupervisedTrainer + device: "@device_list" # "@device" max_epochs: "@max_epochs" train_data_loader: "@train#dataloader" network: "@network" @@ -337,7 +337,7 @@ validate: output_transform: "$monai.handlers.from_engine(['pred', 'label'])" include_background: false additional_metrics: - val_liver_dice: + val_liver_dice: _target_: monai.handlers.MeanDice output_transform: "$monai.handlers.from_engine(['pred_liver', 'label_liver'])" include_background: false @@ -369,4 +369,4 @@ run: - "$print('Labels dict: ', @labels)" - "$print('Get device list: ', scripts.monai_utils.get_device_list(@n_gpu))" #- "$[print(i,': ', data['image'].shape) for i, data in enumerate(@train#dataloader)]" -- "$@train#trainer.run()" \ No newline at end of file +- "$@train#trainer.run()" diff --git a/models/pediatric_abdominal_ct_segmentation/configs/train.yaml b/models/pediatric_abdominal_ct_segmentation/configs/train.yaml index 3d9c0c6b..2fbdc775 100644 --- a/models/pediatric_abdominal_ct_segmentation/configs/train.yaml +++ b/models/pediatric_abdominal_ct_segmentation/configs/train.yaml @@ -23,7 +23,7 @@ train_datalist: "$monai.data.load_decathlon_datalist(@data_list_file_path, data_ val_datalist: "$monai.data.load_decathlon_datalist(@data_list_file_path, data_list_key='validation')" n_gpu: 0 device: "$torch.device('cuda:' + str(@n_gpu) if torch.cuda.is_available() else 'cpu')" -spatial_size: +spatial_size: - 96 - 96 - 96 @@ -51,7 +51,7 @@ network_def: - 2 - 2 - 2 - - + - - 2 - 2 - 1 @@ -64,7 +64,7 @@ network_def: - 2 - 2 - 1 - norm_name: "instance" + norm_name: "instance" deep_supervision: false res_block: true network: "$@network_def.to(@device)" @@ -228,7 +228,7 @@ train: argmax: - true - false - to_onehot: + to_onehot: - "@output_classes" - "@output_classes" - _target_: scripts.monai_utils.SplitPredsLabeld @@ -270,8 +270,8 @@ train: output_transform: "$monai.handlers.from_engine(['pred_pancreas', 'label_pancreas'])" include_background: false trainer: - _target_: SupervisedTrainer - device: "@device" + _target_: SupervisedTrainer + device: "@device" max_epochs: "@max_epochs" train_data_loader: "@train#dataloader" network: "@network" @@ -328,7 +328,7 @@ validate: output_transform: "$monai.handlers.from_engine(['pred', 'label'])" include_background: false additional_metrics: - val_liver_dice: + val_liver_dice: _target_: monai.handlers.MeanDice output_transform: "$monai.handlers.from_engine(['pred_liver', 'label_liver'])" include_background: false @@ -358,4 +358,4 @@ run: - "$print('output_channels: ', @output_channels )" - "$print('spatial_dims: ', @spatial_dims)" - "$print('Labels dict: ', @labels)" -- "$@train#trainer.run()" \ No newline at end of file +- "$@train#trainer.run()" diff --git a/models/pediatric_abdominal_ct_segmentation/docs/README.md b/models/pediatric_abdominal_ct_segmentation/docs/README.md index 8d432e13..85464405 100644 --- a/models/pediatric_abdominal_ct_segmentation/docs/README.md +++ b/models/pediatric_abdominal_ct_segmentation/docs/README.md @@ -1,7 +1,7 @@ # Model Overview -A Pediatric 3D Abdominal Organ Segmentation model, pretrained on adult and pediatric public datasets, and fine tuned for institutional pediatric data. +A Pediatric 3D Abdominal Organ Segmentation model, pretrained on adult and pediatric public datasets, and fine tuned for institutional pediatric data. -Please cite this manuscript: +Please cite this manuscript: Somasundaram E, Taylor Z, Alves VV, et al. Deep-Learning Models for Abdominal CT Organ Segmentation in Children: Development and Validation in Internal and Heterogeneous Public Datasets. AJR 2024 May 1 [published online]. Accepted manuscript. doi:10.2214/AJR.24.30931 ## Data @@ -33,10 +33,10 @@ External dataset licenses can be found in accompanying text file. Internal datas ### Model Architectures - DynUNet - SegResNet -- SwinUNETR +- SwinUNETR ### Hyper-Parameter Tuning -Weights and Biases was used to extensively tune each model for learning rate, scheduler and optimizer. For fine-tuning the fraction of trainable layers was also optimized. DynUNet performed overall better on all test datasets. The Total Segmentator model was also compared and the DynUNet model significantly outperformed Total Segmentator on institutional test data while maintaining relatively stable performance on adult and TCIA datasets. +Weights and Biases was used to extensively tune each model for learning rate, scheduler and optimizer. For fine-tuning the fraction of trainable layers was also optimized. DynUNet performed overall better on all test datasets. The Total Segmentator model was also compared and the DynUNet model significantly outperformed Total Segmentator on institutional test data while maintaining relatively stable performance on adult and TCIA datasets. ### Input One channel CT image diff --git a/models/pediatric_abdominal_ct_segmentation/docs/data_license.txt b/models/pediatric_abdominal_ct_segmentation/docs/data_license.txt index 75144fd4..f4aa3f63 100644 --- a/models/pediatric_abdominal_ct_segmentation/docs/data_license.txt +++ b/models/pediatric_abdominal_ct_segmentation/docs/data_license.txt @@ -32,4 +32,4 @@ Data Usage Agreement / Citations More details about the dataset can be found in the corresponding paper: https://arxiv.org/abs/2208.05868 (the paper describes v1 of the dataset). Please cite this paper if you use the dataset. -Wasserthal, J., Breit, H.-C., Meyer, M. T., Pradella, M., Hinck, D., Sauter, A. W., Heye, T., Boll, D., Cyriac, J., Yang, S., Bach, M., & Segeroth, M. (2023, June 16). TotalSegmentator: Robust segmentation of 104 anatomical structures in CT images. arXiv.org. https://arxiv.org/abs/2208.05868 . https://doi.org/10.1148/ryai.230024 \ No newline at end of file +Wasserthal, J., Breit, H.-C., Meyer, M. T., Pradella, M., Hinck, D., Sauter, A. W., Heye, T., Boll, D., Cyriac, J., Yang, S., Bach, M., & Segeroth, M. (2023, June 16). TotalSegmentator: Robust segmentation of 104 anatomical structures in CT images. arXiv.org. https://arxiv.org/abs/2208.05868 . https://doi.org/10.1148/ryai.230024 diff --git a/models/pediatric_abdominal_ct_segmentation/large_files.yaml b/models/pediatric_abdominal_ct_segmentation/large_files.yaml index e01e241d..293e9e40 100644 --- a/models/pediatric_abdominal_ct_segmentation/large_files.yaml +++ b/models/pediatric_abdominal_ct_segmentation/large_files.yaml @@ -4,4 +4,4 @@ large_files: hash_val: "" hash_type: "" - path: "models/A100/dynunet_FT_trt_16.ts" - url: "https://drive.google.com/file/d/1k44aoW-bXAuG5i9t2nw1qWTidcDsfelQ/view?usp=sharing" \ No newline at end of file + url: "https://drive.google.com/file/d/1k44aoW-bXAuG5i9t2nw1qWTidcDsfelQ/view?usp=sharing" From a92792e2dab335914f8ef916291adb5c95d133bc Mon Sep 17 00:00:00 2001 From: "zach@fastcyberlink.com" Date: Thu, 27 Jun 2024 22:45:55 +0000 Subject: [PATCH 03/10] metadata ignite dependency versioning --- .../configs/metadata.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/models/pediatric_abdominal_ct_segmentation/configs/metadata.json b/models/pediatric_abdominal_ct_segmentation/configs/metadata.json index b6afbc9f..6a12c0c2 100644 --- a/models/pediatric_abdominal_ct_segmentation/configs/metadata.json +++ b/models/pediatric_abdominal_ct_segmentation/configs/metadata.json @@ -27,7 +27,7 @@ "optional_packages_version": { "fire": "0.4.0", "nibabel": "4.0.1", - "pytorch-ignite": "0.4.9" + "pytorch-ignite": "0.4.11" }, "name": "CT-Ped-Abdominal-Seg", "task": "Training and Prediction of 3D Segmentation of Liver, Spleen and Pancreas from Abdominal CT images", @@ -40,7 +40,7 @@ "label_classes": "single channel data, 1 is liver, 2 is spleen, 3 is pancreas and 0 is everything else", "pred_classes": "single channel data, 1 is liver, 2 is spleen, 3 is pancreas and 0 is everything else", "eval_metrics": { - "TS_mean_dice": 0.9, + "TS_mean_dice": 0.90, "TCIA_mean_dice": 0.87, "CCHMC_mean_dice": 0.89 }, @@ -98,4 +98,4 @@ } } } -} +} \ No newline at end of file From a6973676b01c1d309213de869fa48c63d6960156 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Thu, 27 Jun 2024 22:46:10 +0000 Subject: [PATCH 04/10] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- .../pediatric_abdominal_ct_segmentation/configs/metadata.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/models/pediatric_abdominal_ct_segmentation/configs/metadata.json b/models/pediatric_abdominal_ct_segmentation/configs/metadata.json index 6a12c0c2..6f49c389 100644 --- a/models/pediatric_abdominal_ct_segmentation/configs/metadata.json +++ b/models/pediatric_abdominal_ct_segmentation/configs/metadata.json @@ -40,7 +40,7 @@ "label_classes": "single channel data, 1 is liver, 2 is spleen, 3 is pancreas and 0 is everything else", "pred_classes": "single channel data, 1 is liver, 2 is spleen, 3 is pancreas and 0 is everything else", "eval_metrics": { - "TS_mean_dice": 0.90, + "TS_mean_dice": 0.9, "TCIA_mean_dice": 0.87, "CCHMC_mean_dice": 0.89 }, @@ -98,4 +98,4 @@ } } } -} \ No newline at end of file +} From 6acc3f25ce0505af58991b2ee27635b15664d4c1 Mon Sep 17 00:00:00 2001 From: --gituser Date: Mon, 15 Jul 2024 23:25:27 +0000 Subject: [PATCH 05/10] ci_custom_data_exclusion --- ci/bundle_custom_data.py | 4 +++- .../scripts/compute_metric.py | 1 - 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/ci/bundle_custom_data.py b/ci/bundle_custom_data.py index 711bce3f..6a470053 100644 --- a/ci/bundle_custom_data.py +++ b/ci/bundle_custom_data.py @@ -23,7 +23,9 @@ # This list is used for our CI tests to determine whether a bundle contains the preferred files. # If a bundle does not have any of the preferred files, please add the bundle name into the list. -exclude_verify_preferred_files_list = [] +exclude_verify_preferred_files_list = [ + "pediatric_abdominal_ct_segmentation" +] # This list is used for our CI tests to determine whether a bundle needs to be tested with # the `verify_export_torchscript` function in `verify_bundle.py`. diff --git a/models/pediatric_abdominal_ct_segmentation/scripts/compute_metric.py b/models/pediatric_abdominal_ct_segmentation/scripts/compute_metric.py index 1c7bb1c2..23bab5dd 100644 --- a/models/pediatric_abdominal_ct_segmentation/scripts/compute_metric.py +++ b/models/pediatric_abdominal_ct_segmentation/scripts/compute_metric.py @@ -59,7 +59,6 @@ ToTensord, ) from monai.utils import string_list_all_gather - from scripts.monai_utils import CopyFilenamesd From 86bdcd94b9ea447612c63366181bea322cf7227f Mon Sep 17 00:00:00 2001 From: Yiheng Wang Date: Tue, 16 Jul 2024 15:56:32 +0800 Subject: [PATCH 06/10] fix format issue Signed-off-by: Yiheng Wang --- ci/bundle_custom_data.py | 4 +- .../scripts/compute_metric.py | 13 +-- .../scripts/lr_scheduler.py | 65 +++----------- .../scripts/monai_utils.py | 25 ++---- .../scripts/prepare_datalist_monailabel.py | 89 ++++--------------- 5 files changed, 41 insertions(+), 155 deletions(-) diff --git a/ci/bundle_custom_data.py b/ci/bundle_custom_data.py index 6a470053..a05971aa 100644 --- a/ci/bundle_custom_data.py +++ b/ci/bundle_custom_data.py @@ -23,9 +23,7 @@ # This list is used for our CI tests to determine whether a bundle contains the preferred files. # If a bundle does not have any of the preferred files, please add the bundle name into the list. -exclude_verify_preferred_files_list = [ - "pediatric_abdominal_ct_segmentation" -] +exclude_verify_preferred_files_list = ["pediatric_abdominal_ct_segmentation"] # This list is used for our CI tests to determine whether a bundle needs to be tested with # the `verify_export_torchscript` function in `verify_bundle.py`. diff --git a/models/pediatric_abdominal_ct_segmentation/scripts/compute_metric.py b/models/pediatric_abdominal_ct_segmentation/scripts/compute_metric.py index 23bab5dd..55a6d6f4 100644 --- a/models/pediatric_abdominal_ct_segmentation/scripts/compute_metric.py +++ b/models/pediatric_abdominal_ct_segmentation/scripts/compute_metric.py @@ -70,10 +70,7 @@ def compute(datalist, output_dir): # split data for every subprocess, for example, 16 processes compute in parallel data_part = partition_dataset( - data=datalist, - num_partitions=dist.get_world_size(), - shuffle=False, - even_divisible=False, + data=datalist, num_partitions=dist.get_world_size(), shuffle=False, even_divisible=False )[dist.get_rank()] device = torch.device(f"cuda:{local_rank}") @@ -87,9 +84,7 @@ def compute(datalist, output_dir): ToDeviced(keys=["pred", "label"], device=device), EnsureChannelFirstd(keys=["pred", "label"]), Orientationd(keys=("pred", "label"), axcodes="RAS"), - AsDiscreted( - keys=("pred", "label"), argmax=(False, False), to_onehot=(4, 4) - ), + AsDiscreted(keys=("pred", "label"), argmax=(False, False), to_onehot=(4, 4)), ] ) @@ -135,9 +130,7 @@ def compute_single_node(datalist, output_dir): EnsureChannelFirstd(keys=["pred", "label"]), Orientationd(keys=("pred", "label"), axcodes="RAS"), AddLabelNamesd(keys=("pred", "label"), label_names=labels), - AsDiscreted( - keys=("pred", "label"), argmax=(False, False), to_onehot=(4, 4) - ), + AsDiscreted(keys=("pred", "label"), argmax=(False, False), to_onehot=(4, 4)), ] ) data_part = [transforms(item) for item in data_part] diff --git a/models/pediatric_abdominal_ct_segmentation/scripts/lr_scheduler.py b/models/pediatric_abdominal_ct_segmentation/scripts/lr_scheduler.py index 4a93080d..0c352927 100644 --- a/models/pediatric_abdominal_ct_segmentation/scripts/lr_scheduler.py +++ b/models/pediatric_abdominal_ct_segmentation/scripts/lr_scheduler.py @@ -24,9 +24,7 @@ class _LRSchedulerMONAI(_LRScheduler): """Base class for increasing the learning rate between two boundaries over a number of iterations""" - def __init__( - self, optimizer: Optimizer, end_lr: float, num_iter: int, last_epoch: int = -1 - ) -> None: + def __init__(self, optimizer: Optimizer, end_lr: float, num_iter: int, last_epoch: int = -1) -> None: """ Args: optimizer: wrapped optimizer. @@ -67,12 +65,7 @@ class WarmupCosineSchedule(LambdaLR): """ def __init__( - self, - optimizer: Optimizer, - warmup_steps: int, - t_total: int, - cycles: float = 0.5, - last_epoch: int = -1, + self, optimizer: Optimizer, warmup_steps: int, t_total: int, cycles: float = 0.5, last_epoch: int = -1 ) -> None: """ Args: @@ -87,19 +80,13 @@ def __init__( self.warmup_steps = warmup_steps self.t_total = t_total self.cycles = cycles - super(WarmupCosineSchedule, self).__init__( - optimizer, self.lr_lambda, last_epoch - ) + super(WarmupCosineSchedule, self).__init__(optimizer, self.lr_lambda, last_epoch) def lr_lambda(self, step): if step < self.warmup_steps: return float(step) / float(max(1.0, self.warmup_steps)) - progress = float(step - self.warmup_steps) / float( - max(1, self.t_total - self.warmup_steps) - ) - return max( - 0.0, 0.5 * (1.0 + math.cos(math.pi * float(self.cycles) * 2.0 * progress)) - ) + progress = float(step - self.warmup_steps) / float(max(1, self.t_total - self.warmup_steps)) + return max(0.0, 0.5 * (1.0 + math.cos(math.pi * float(self.cycles) * 2.0 * progress))) class LinearWarmupCosineAnnealingLR(_LRScheduler): @@ -134,47 +121,31 @@ def get_lr(self) -> List[float]: """ if not self._get_lr_called_within_step: warnings.warn( - "To get the last learning rate computed by the scheduler, " - "please use `get_last_lr()`.", - UserWarning, + "To get the last learning rate computed by the scheduler, " "please use `get_last_lr()`.", UserWarning ) if self.last_epoch == 0: return [self.warmup_start_lr] * len(self.base_lrs) elif self.last_epoch < self.warmup_epochs: return [ - group["lr"] - + (base_lr - self.warmup_start_lr) / (self.warmup_epochs - 1) + group["lr"] + (base_lr - self.warmup_start_lr) / (self.warmup_epochs - 1) for base_lr, group in zip(self.base_lrs, self.optimizer.param_groups) ] elif self.last_epoch == self.warmup_epochs: return self.base_lrs - elif (self.last_epoch - 1 - self.max_epochs) % ( - 2 * (self.max_epochs - self.warmup_epochs) - ) == 0: + elif (self.last_epoch - 1 - self.max_epochs) % (2 * (self.max_epochs - self.warmup_epochs)) == 0: return [ group["lr"] - + (base_lr - self.eta_min) - * (1 - math.cos(math.pi / (self.max_epochs - self.warmup_epochs))) - / 2 + + (base_lr - self.eta_min) * (1 - math.cos(math.pi / (self.max_epochs - self.warmup_epochs))) / 2 for base_lr, group in zip(self.base_lrs, self.optimizer.param_groups) ] return [ - ( - 1 - + math.cos( - math.pi - * (self.last_epoch - self.warmup_epochs) - / (self.max_epochs - self.warmup_epochs) - ) - ) + (1 + math.cos(math.pi * (self.last_epoch - self.warmup_epochs) / (self.max_epochs - self.warmup_epochs))) / ( 1 + math.cos( - math.pi - * (self.last_epoch - self.warmup_epochs - 1) - / (self.max_epochs - self.warmup_epochs) + math.pi * (self.last_epoch - self.warmup_epochs - 1) / (self.max_epochs - self.warmup_epochs) ) ) * (group["lr"] - self.eta_min) @@ -188,10 +159,7 @@ def _get_closed_form_lr(self) -> List[float]: """ if self.last_epoch < self.warmup_epochs: return [ - self.warmup_start_lr - + self.last_epoch - * (base_lr - self.warmup_start_lr) - / (self.warmup_epochs - 1) + self.warmup_start_lr + self.last_epoch * (base_lr - self.warmup_start_lr) / (self.warmup_epochs - 1) for base_lr in self.base_lrs ] @@ -199,13 +167,6 @@ def _get_closed_form_lr(self) -> List[float]: self.eta_min + 0.5 * (base_lr - self.eta_min) - * ( - 1 - + math.cos( - math.pi - * (self.last_epoch - self.warmup_epochs) - / (self.max_epochs - self.warmup_epochs) - ) - ) + * (1 + math.cos(math.pi * (self.last_epoch - self.warmup_epochs) / (self.max_epochs - self.warmup_epochs))) for base_lr in self.base_lrs ] diff --git a/models/pediatric_abdominal_ct_segmentation/scripts/monai_utils.py b/models/pediatric_abdominal_ct_segmentation/scripts/monai_utils.py index 0226abc5..a22b76f5 100644 --- a/models/pediatric_abdominal_ct_segmentation/scripts/monai_utils.py +++ b/models/pediatric_abdominal_ct_segmentation/scripts/monai_utils.py @@ -78,9 +78,7 @@ def SupervisedTrainer_multi_gpu( net = network if distributed: if len(devices_) > 1: - raise ValueError( - f"for distributed training, `devices` must contain only 1 GPU or CPU, but got {devices_}." - ) + raise ValueError(f"for distributed training, `devices` must contain only 1 GPU or CPU, but got {devices_}.") net = DistributedDataParallel(net, device_ids=devices_) elif len(devices_) > 1: net = DataParallel(net, device_ids=devices_) # ,output_device=devices_[0]) @@ -143,9 +141,7 @@ def __init__( ) self.net = DistributedDataParallel(self.net, device_ids=self.devices_) elif len(self.devices_) > 1: - self.net = DataParallel( - self.net, device_ids=self.devices_ - ) # ,output_device=devices_[0]) + self.net = DataParallel(self.net, device_ids=self.devices_) # ,output_device=devices_[0]) super().__init__( device=self.devices_[0], @@ -168,10 +164,7 @@ def __init__( class AddLabelNamesd(MapTransform): def __init__( - self, - keys: KeysCollection, - label_names: dict[str, int] | None = None, - allow_missing_keys: bool = False, + self, keys: KeysCollection, label_names: dict[str, int] | None = None, allow_missing_keys: bool = False ): """ Normalize label values according to label names dictionary @@ -184,9 +177,7 @@ def __init__( self.label_names = label_names or {} - def __call__( - self, data: Mapping[Hashable, np.ndarray] - ) -> dict[Hashable, np.ndarray]: + def __call__(self, data: Mapping[Hashable, np.ndarray]) -> dict[Hashable, np.ndarray]: d: dict = dict(data) d["label_names"] = self.label_names return d @@ -202,9 +193,7 @@ def __init__(self, keys: KeysCollection, allow_missing_keys: bool = False): """ super().__init__(keys, allow_missing_keys) - def __call__( - self, data: Mapping[Hashable, np.ndarray] - ) -> dict[Hashable, np.ndarray]: + def __call__(self, data: Mapping[Hashable, np.ndarray]) -> dict[Hashable, np.ndarray]: d: dict = dict(data) d["filename"] = os.path.basename(d["label"]) return d @@ -216,9 +205,7 @@ class SplitPredsLabeld(MapTransform): """ - def __call__( - self, data: Mapping[Hashable, np.ndarray] - ) -> dict[Hashable, np.ndarray]: + def __call__(self, data: Mapping[Hashable, np.ndarray]) -> dict[Hashable, np.ndarray]: d: dict = dict(data) for key in self.key_iterator(d): if key == "pred": diff --git a/models/pediatric_abdominal_ct_segmentation/scripts/prepare_datalist_monailabel.py b/models/pediatric_abdominal_ct_segmentation/scripts/prepare_datalist_monailabel.py index 0e4bdfe9..b9913d0a 100644 --- a/models/pediatric_abdominal_ct_segmentation/scripts/prepare_datalist_monailabel.py +++ b/models/pediatric_abdominal_ct_segmentation/scripts/prepare_datalist_monailabel.py @@ -10,10 +10,7 @@ def produce_datalist_splits( - datalist, - splits: list = ["test"], - train_split: float = 0.80, - valid_test_split: float = 0.50, + datalist, splits: list = ["test"], train_split: float = 0.80, valid_test_split: float = 0.50 ): """ This function is used to split the dataset. @@ -22,14 +19,8 @@ def produce_datalist_splits( if "train" in splits: train_list, other_list = train_test_split(datalist, train_size=train_split) if "valid" in splits: - val_list, test_list = train_test_split( - other_list, train_size=valid_test_split - ) - return { - "training": train_list, - "validation": val_list, - "testing": test_list, - } + val_list, test_list = train_test_split(other_list, train_size=valid_test_split) + return {"training": train_list, "validation": val_list, "testing": test_list} else: return {"training": train_list, "testing": other_list} elif "valid" in splits: @@ -52,21 +43,15 @@ def keep_image_label_pairs_only(a_images, a_labels, i_folder, l_folder): # print('Data folder: ',a_images[0].split('/')[-2]) # print('Labels missing for: ', len(labelmissing)) # print('Images missing for: ', len(imagemissing)) - a_images = sorted( - [os.path.join(i_folder, a) for a in image_names if a in label_names] - ) + a_images = sorted([os.path.join(i_folder, a) for a in image_names if a in label_names]) ## Keep only labels that have a scan image_names = [a.split("/")[-1] for a in a_images] - a_labels = sorted( - [os.path.join(l_folder, a) for a in label_names if a in image_names] - ) + a_labels = sorted([os.path.join(l_folder, a) for a in label_names if a in image_names]) return a_images, a_labels def parse_files(images_folder, labels_folder, file_extension_pattern): - logging.info( - f"parsing files at: {os.path.join(images_folder, file_extension_pattern)}" - ) + logging.info(f"parsing files at: {os.path.join(images_folder, file_extension_pattern)}") all_images = sorted(glob.glob(os.path.join(images_folder, file_extension_pattern))) all_labels = sorted(glob.glob(os.path.join(labels_folder, file_extension_pattern))) return all_images, all_labels @@ -79,26 +64,17 @@ def get_datalist(args, images_folder, labels_folder): all_labels = [] for ifolder, lfolder in zip(images_folder, labels_folder): a_images, a_labels = parse_files(ifolder, lfolder, file_extension_pattern) - a_images, a_labels = keep_image_label_pairs_only( - a_images, a_labels, ifolder, lfolder - ) + a_images, a_labels = keep_image_label_pairs_only(a_images, a_labels, ifolder, lfolder) all_images += a_images all_labels += a_labels else: - all_images, all_labels = parse_files( - images_folder, labels_folder, file_extension_pattern - ) - all_images, all_labels = keep_image_label_pairs_only( - all_images, all_labels, images_folder, labels_folder - ) + all_images, all_labels = parse_files(images_folder, labels_folder, file_extension_pattern) + all_images, all_labels = keep_image_label_pairs_only(all_images, all_labels, images_folder, labels_folder) logging.info("Length of all_images: {}".format(len(all_images))) logging.info("Length of all_labels: {}".format(len(all_labels))) - datalist = [ - {"image": image_name, "label": label_name} - for image_name, label_name in zip(all_images, all_labels) - ] + datalist = [{"image": image_name, "label": label_name} for image_name, label_name in zip(all_images, all_labels)] # datalist = datalist[0 : args.limit] if args.limit else datalist logging.info(f"datalist length is {len(datalist)}") @@ -113,12 +89,8 @@ def main(args): output_json = args.output # produce deterministic data splits monai.utils.set_determinism(seed=123) - datalist = get_datalist( - args, data_file_base_dir, os.path.join(data_file_base_dir, args.labels_folder) - ) - datalist = produce_datalist_splits( - datalist, args.splits, args.train_split, args.valid_test_split - ) + datalist = get_datalist(args, data_file_base_dir, os.path.join(data_file_base_dir, args.labels_folder)) + datalist = produce_datalist_splits(datalist, args.splits, args.train_split, args.valid_test_split) with open(output_json, "w") as f: json.dump(datalist, f, ensure_ascii=True, indent=4) logging.info("datalist json file saved to: {}".format(output_json)) @@ -139,38 +111,13 @@ def main(args): help="root path of MSD Task07_Pancreas dataset.", ) parser.add_argument( - "--output", - type=str, - default="dataset_0.json", - help="relative path of output datalist json file.", - ) - parser.add_argument( - "--train_split", type=int, default=0.80, help="fraction of Training samples." - ) - parser.add_argument( - "--valid_test_split", - type=int, - default=0.50, - help="fraction of valid/test samples.", - ) - parser.add_argument( - "--splits", - type=list, - default=["test"], - help="splits to use for train, valid, and test.", - ) - parser.add_argument( - "--file_extension", - type=str, - default="nii", - help="file extension of images and labels.", - ) - parser.add_argument( - "--labels_folder", - type=str, - default="labels/final", - help="labels sub folder name", + "--output", type=str, default="dataset_0.json", help="relative path of output datalist json file." ) + parser.add_argument("--train_split", type=int, default=0.80, help="fraction of Training samples.") + parser.add_argument("--valid_test_split", type=int, default=0.50, help="fraction of valid/test samples.") + parser.add_argument("--splits", type=list, default=["test"], help="splits to use for train, valid, and test.") + parser.add_argument("--file_extension", type=str, default="nii", help="file extension of images and labels.") + parser.add_argument("--labels_folder", type=str, default="labels/final", help="labels sub folder name") args = parser.parse_args() From 868bb05cf9616259602db4fa3df2f006f1787b09 Mon Sep 17 00:00:00 2001 From: emergentbehaviour Date: Wed, 7 Aug 2024 16:29:13 +0000 Subject: [PATCH 07/10] readme and codeformat corrections --- .../configs/metadata.json | 21 +--------- .../configs/train-multigpu.yaml | 2 +- .../docs/README.md | 19 ++++++++++ .../scripts/compute_metric.py | 12 ++---- .../scripts/lr_scheduler.py | 3 +- .../scripts/monai_utils.py | 38 +++++++++---------- .../scripts/prepare_datalist_monailabel.py | 16 ++++---- 7 files changed, 52 insertions(+), 59 deletions(-) diff --git a/models/pediatric_abdominal_ct_segmentation/configs/metadata.json b/models/pediatric_abdominal_ct_segmentation/configs/metadata.json index 6f49c389..0ae078f0 100644 --- a/models/pediatric_abdominal_ct_segmentation/configs/metadata.json +++ b/models/pediatric_abdominal_ct_segmentation/configs/metadata.json @@ -1,25 +1,8 @@ { "schema": "https://github.com/Project-MONAI/MONAI-extra-test-data/releases/download/0.8.1/meta_schema_20220324.json", - "version": "0.4.3", + "version": "0.4.4", "changelog": { - "0.4.3": "add support for TensorRT conversion and inference", - "0.4.2": "update search function to match monai 1.2", - "0.4.1": "fix the wrong GPU index issue of multi-node", - "0.4.0": "remove error dollar symbol in readme", - "0.3.9": "add cpu ram requirement in readme", - "0.3.8": "add non-deterministic note", - "0.3.7": "re-train model with updated dints implementation", - "0.3.6": "black autofix format and add name tag", - "0.3.5": "restructure readme to match updated template", - "0.3.4": "correct typos", - "0.3.3": "update learning rate and readme", - "0.3.2": "update to use monai 1.0.1", - "0.3.1": "fix license Copyright error", - "0.3.0": "update license files", - "0.2.0": "unify naming", - "0.1.1": "fix data type issue in searching/training configurations", - "0.1.0": "complete the model package", - "0.0.1": "initialize the model package structure" + "0.4.4": "initial bundle assemblage." }, "monai_version": "1.3.0", "pytorch_version": "2.1.0", diff --git a/models/pediatric_abdominal_ct_segmentation/configs/train-multigpu.yaml b/models/pediatric_abdominal_ct_segmentation/configs/train-multigpu.yaml index 2559b812..0dd89e95 100644 --- a/models/pediatric_abdominal_ct_segmentation/configs/train-multigpu.yaml +++ b/models/pediatric_abdominal_ct_segmentation/configs/train-multigpu.yaml @@ -278,7 +278,7 @@ train: output_transform: "$monai.handlers.from_engine(['pred_pancreas', 'label_pancreas'])" include_background: false trainer: - _target_: scripts.monai_utils.SupervisedTrainer_m_gpu # SupervisedTrainer + _target_: scripts.monai_utils.SupervisedTrainerMGPU # SupervisedTrainer device: "@device_list" # "@device" max_epochs: "@max_epochs" train_data_loader: "@train#dataloader" diff --git a/models/pediatric_abdominal_ct_segmentation/docs/README.md b/models/pediatric_abdominal_ct_segmentation/docs/README.md index 85464405..df8e3c36 100644 --- a/models/pediatric_abdominal_ct_segmentation/docs/README.md +++ b/models/pediatric_abdominal_ct_segmentation/docs/README.md @@ -30,6 +30,25 @@ Testing data: External dataset licenses can be found in accompanying text file. Internal datasets currently not publicly available. +To load data for training / inference / evaluate: + +Ensure that the "image" and "label" parameters within the "training" and "validation" sections in configs/TS_test.json (or a new dataset json), as well as the "datalist" and "dataset_dir" in configs/train.yaml, configs/inference.yaml, and configs/evaluate-standalone.yaml files (or the according yaml if using multigpu / parallel or different model inferencing) are each changed to match the intended dataset's values. + +One may make separate .json files detailing which exam images / masks are to be used in the same format as configs/TS_test.json with "training" and "validation" under root, as long as the "datalist_file_path" and "dataset_dir" values is changed accordingly in configs/train.yaml and configs/inference.yaml, and configs/evaluate-standalone.yaml (or the according yaml in different circumstances). + +Ensure data folder structure is as follows, with scan files in the primary dataset folder, and mask files in the /labels/final subfolder: + dataset/ + ├─ exam_001.nii.gz + ├─ exam_002.nii.gz + ├─ ... + ├─ labels/ + │ ├─ final/ + │ │ ├─ exam_001.nii.gz + │ │ ├─ exam_002.nii.gz + │ │ ├─ ... + +Configuration defaults are currently set to the external TotalSegmentator CT dataset. + ### Model Architectures - DynUNet - SegResNet diff --git a/models/pediatric_abdominal_ct_segmentation/scripts/compute_metric.py b/models/pediatric_abdominal_ct_segmentation/scripts/compute_metric.py index 55a6d6f4..def4d7a6 100644 --- a/models/pediatric_abdominal_ct_segmentation/scripts/compute_metric.py +++ b/models/pediatric_abdominal_ct_segmentation/scripts/compute_metric.py @@ -36,27 +36,21 @@ """ -import argparse import os -from glob import glob -import nibabel as nib -import numpy as np import torch import torch.distributed as dist -from monai.data import create_test_image_3d, partition_dataset +from monai.data import partition_dataset from monai.handlers import write_metrics_reports from monai.metrics import DiceMetric from monai.transforms import ( + AddLabelNamesd, AsDiscreted, Compose, EnsureChannelFirstd, - KeepLargestConnectedComponentd, LoadImaged, Orientationd, - ScaleIntensityd, ToDeviced, - ToTensord, ) from monai.utils import string_list_all_gather from scripts.monai_utils import CopyFilenamesd @@ -115,6 +109,8 @@ def compute(datalist, output_dir): def compute_single_node(datalist, output_dir): + local_rank = int(os.environ["LOCAL_RANK"]) + filenames = [d["label"].split("/")[-1] for d in datalist] data_part = datalist diff --git a/models/pediatric_abdominal_ct_segmentation/scripts/lr_scheduler.py b/models/pediatric_abdominal_ct_segmentation/scripts/lr_scheduler.py index 0c352927..57c159fc 100644 --- a/models/pediatric_abdominal_ct_segmentation/scripts/lr_scheduler.py +++ b/models/pediatric_abdominal_ct_segmentation/scripts/lr_scheduler.py @@ -13,8 +13,7 @@ import warnings from typing import List -from torch import nn as nn -from torch.optim import Adam, Optimizer +from torch.optim import Optimizer from torch.optim.lr_scheduler import LambdaLR, _LRScheduler __all__ = ["LinearLR", "ExponentialLR"] diff --git a/models/pediatric_abdominal_ct_segmentation/scripts/monai_utils.py b/models/pediatric_abdominal_ct_segmentation/scripts/monai_utils.py index a22b76f5..6dc2d5eb 100644 --- a/models/pediatric_abdominal_ct_segmentation/scripts/monai_utils.py +++ b/models/pediatric_abdominal_ct_segmentation/scripts/monai_utils.py @@ -1,18 +1,22 @@ from __future__ import annotations -import json import logging -import random -import warnings -from collections.abc import Hashable, Mapping, Sequence, Sized +import os +from collections.abc import Hashable, Mapping +from typing import Any, Callable, Sequence import numpy as np import torch +import torch.nn +from ignite.engine import Engine +from ignite.metrics import Metric from monai.config import KeysCollection -from monai.data import MetaTensor -from monai.networks.layers import GaussianFilter -from monai.transforms.transform import MapTransform, Randomizable, Transform -from monai.utils import min_version, optional_import +from monai.engines import SupervisedTrainer +from monai.engines.utils import get_devices_spec +from monai.inferers import Inferer +from monai.transforms.transform import MapTransform, Transform +from torch.nn.parallel import DataParallel, DistributedDataParallel +from torch.optim.optimizer import Optimizer # measure, _ = optional_import("skimage.measure", "0.14.2", min_version) @@ -20,15 +24,6 @@ # distance_transform_cdt, _ = optional_import("scipy.ndimage.morphology", name="distance_transform_cdt") -import os -from typing import TYPE_CHECKING, Any, Callable, Iterable, Sequence - -import torch.nn -from monai.engines import SupervisedTrainer -from monai.engines.utils import get_devices_spec -from torch.nn.parallel import DataParallel, DistributedDataParallel -from torch.optim.optimizer import Optimizer - def get_device_list(n_gpu): if type(n_gpu) is not list: @@ -38,15 +33,16 @@ def get_device_list(n_gpu): device_list = [d for d in device_list if d in n_gpu] else: logging.info( - "Highest GPU ID provided in 'n_gpu' is larger than number of GPUs available, assigning GPUs starting from 0 to match n_gpu length of {}".format( + """Highest GPU ID provided in 'n_gpu' is larger than number of GPUs available, assigning GPUs starting from 0 + to match n_gpu length of {}""".format( len(n_gpu) ) ) - device_list = [d for d in device_list][: len(n_gpu)] + device_list = device_list[: len(n_gpu)] return device_list -def SupervisedTrainer_multi_gpu( +def supervised_trainer_multi_gpu( max_epochs: int, train_data_loader, network: torch.nn.Module, @@ -102,7 +98,7 @@ def SupervisedTrainer_multi_gpu( ) -class SupervisedTrainer_m_gpu(SupervisedTrainer): +class SupervisedTrainerMGPU(SupervisedTrainer): def __init__( self, max_epochs: int, diff --git a/models/pediatric_abdominal_ct_segmentation/scripts/prepare_datalist_monailabel.py b/models/pediatric_abdominal_ct_segmentation/scripts/prepare_datalist_monailabel.py index b9913d0a..05885d98 100644 --- a/models/pediatric_abdominal_ct_segmentation/scripts/prepare_datalist_monailabel.py +++ b/models/pediatric_abdominal_ct_segmentation/scripts/prepare_datalist_monailabel.py @@ -9,13 +9,13 @@ from sklearn.model_selection import train_test_split -def produce_datalist_splits( - datalist, splits: list = ["test"], train_split: float = 0.80, valid_test_split: float = 0.50 -): +def produce_datalist_splits(datalist, splits: list = None, train_split: float = 0.80, valid_test_split: float = 0.50): """ This function is used to split the dataset. It will produce "train_size" number of samples for training. """ + if splits is None: + splits = ["test"] if "train" in splits: train_list, other_list = train_test_split(datalist, train_size=train_split) if "valid" in splits: @@ -34,17 +34,17 @@ def keep_image_label_pairs_only(a_images, a_labels, i_folder, l_folder): image_names = [a.split("/")[-1] for a in a_images] label_names = [a.split("/")[-1] for a in a_labels] # Check if all_labels == all_images, if all_images < all_labels, truncate all_labels - image_set = set(image_names) - label_set = set(label_names) - labelmissing = image_set.difference(label_set) + # image_set = set(image_names) + # label_set = set(label_names) + # labelmissing = image_set.difference(label_set) # Find names labels not in images - imagemissing = label_set.difference(image_set) + # imagemissing = label_set.difference(image_set) # print('Data_path: ', a_images[0]) # print('Data folder: ',a_images[0].split('/')[-2]) # print('Labels missing for: ', len(labelmissing)) # print('Images missing for: ', len(imagemissing)) a_images = sorted([os.path.join(i_folder, a) for a in image_names if a in label_names]) - ## Keep only labels that have a scan + # Keep only labels that have a scan image_names = [a.split("/")[-1] for a in a_images] a_labels = sorted([os.path.join(l_folder, a) for a in label_names if a in image_names]) return a_images, a_labels From d6fccb4f9fc843b88875fd98041184a9bd6910c0 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Wed, 7 Aug 2024 16:54:30 +0000 Subject: [PATCH 08/10] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- models/pediatric_abdominal_ct_segmentation/docs/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/models/pediatric_abdominal_ct_segmentation/docs/README.md b/models/pediatric_abdominal_ct_segmentation/docs/README.md index df8e3c36..a4bd4c52 100644 --- a/models/pediatric_abdominal_ct_segmentation/docs/README.md +++ b/models/pediatric_abdominal_ct_segmentation/docs/README.md @@ -46,7 +46,7 @@ Ensure data folder structure is as follows, with scan files in the primary datas │ │ ├─ exam_001.nii.gz │ │ ├─ exam_002.nii.gz │ │ ├─ ... - + Configuration defaults are currently set to the external TotalSegmentator CT dataset. ### Model Architectures From ba0bbb052bbccf40ef8eb761b8250fb392ca431b Mon Sep 17 00:00:00 2001 From: Yiheng Wang Date: Tue, 3 Sep 2024 08:14:00 +0000 Subject: [PATCH 09/10] update ci test script Signed-off-by: Yiheng Wang --- ci/verify_bundle.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/ci/verify_bundle.py b/ci/verify_bundle.py index 97ea80a5..623a2c94 100644 --- a/ci/verify_bundle.py +++ b/ci/verify_bundle.py @@ -48,6 +48,8 @@ def _get_weights_names(bundle: str): return "model_autoencoder.pt", "model_autoencoder.ts" if bundle == "brats_mri_axial_slices_generative_diffusion": return "model_autoencoder.pt", None + if bundle == "pediatric_abdominal_ct_segmentation": + return "dynunet_FT.pt", "A100/dynunet_FT_trt_16.ts" return "model.pt", "model.ts" From 2ecf1cb779ec0852ad4c71c4b27394baedf2ba85 Mon Sep 17 00:00:00 2001 From: Yiheng Wang Date: Fri, 6 Sep 2024 10:23:21 +0800 Subject: [PATCH 10/10] adjust verify ts Signed-off-by: Yiheng Wang --- ci/verify_bundle.py | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/ci/verify_bundle.py b/ci/verify_bundle.py index 623a2c94..aa8f218e 100644 --- a/ci/verify_bundle.py +++ b/ci/verify_bundle.py @@ -49,7 +49,8 @@ def _get_weights_names(bundle: str): if bundle == "brats_mri_axial_slices_generative_diffusion": return "model_autoencoder.pt", None if bundle == "pediatric_abdominal_ct_segmentation": - return "dynunet_FT.pt", "A100/dynunet_FT_trt_16.ts" + # skip test for this bundle's ts file + return "dynunet_FT.pt", None return "model.pt", "model.ts" @@ -213,11 +214,11 @@ def verify_torchscript( bundle_root=bundle_path, ) print("export weights into TorchScript module successfully.") - - ts_model_path = os.path.join(bundle_path, "models", ts_name) - if os.path.exists(ts_model_path): - torch.jit.load(ts_model_path) - print("Provided TorchScript module is verified correctly.") + if ts_name is not None: + ts_model_path = os.path.join(bundle_path, "models", ts_name) + if os.path.exists(ts_model_path): + torch.jit.load(ts_model_path) + print("Provided TorchScript module is verified correctly.") def get_app_properties(app: str, version: str):