diff --git a/ci/bundle_custom_data.py b/ci/bundle_custom_data.py index 5097a030..92613b97 100644 --- a/ci/bundle_custom_data.py +++ b/ci/bundle_custom_data.py @@ -24,7 +24,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 = ["maisi_ct_generative"] +exclude_verify_preferred_files_list = ["pediatric_abdominal_ct_segmentation", "maisi_ct_generative"] # 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/ci/verify_bundle.py b/ci/verify_bundle.py index 97ea80a5..aa8f218e 100644 --- a/ci/verify_bundle.py +++ b/ci/verify_bundle.py @@ -48,6 +48,9 @@ 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": + # skip test for this bundle's ts file + return "dynunet_FT.pt", None return "model.pt", "model.ts" @@ -211,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): 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..6088acfc --- /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" + } + ] +} 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..2e0af963 --- /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)" 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..229d0207 --- /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)" 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..f0605bc7 --- /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()" 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..11f952f5 --- /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()" 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..3847fb6c --- /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()" 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..91c1a21c --- /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 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..0ae078f0 --- /dev/null +++ b/models/pediatric_abdominal_ct_segmentation/configs/metadata.json @@ -0,0 +1,84 @@ +{ + "schema": "https://github.com/Project-MONAI/MONAI-extra-test-data/releases/download/0.8.1/meta_schema_20220324.json", + "version": "0.4.4", + "changelog": { + "0.4.4": "initial bundle assemblage." + }, + "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.11" + }, + "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.9, + "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" + } + } + } + } +} 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..0dd89e95 --- /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.SupervisedTrainerMGPU # 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()" 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..2fbdc775 --- /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()" 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..a4bd4c52 --- /dev/null +++ b/models/pediatric_abdominal_ct_segmentation/docs/README.md @@ -0,0 +1,164 @@ +# 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. + +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 +- 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..f4aa3f63 --- /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 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..293e9e40 --- /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" 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..def4d7a6 --- /dev/null +++ b/models/pediatric_abdominal_ct_segmentation/scripts/compute_metric.py @@ -0,0 +1,150 @@ +# 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 os + +import torch +import torch.distributed as dist +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, + LoadImaged, + Orientationd, + ToDeviced, +) +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): + local_rank = int(os.environ["LOCAL_RANK"]) + + 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..57c159fc --- /dev/null +++ b/models/pediatric_abdominal_ct_segmentation/scripts/lr_scheduler.py @@ -0,0 +1,171 @@ +# 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.optim import 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..6dc2d5eb --- /dev/null +++ b/models/pediatric_abdominal_ct_segmentation/scripts/monai_utils.py @@ -0,0 +1,214 @@ +from __future__ import annotations + +import logging +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.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) + +logger = logging.getLogger(__name__) + +# distance_transform_cdt, _ = optional_import("scipy.ndimage.morphology", name="distance_transform_cdt") + + +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 = device_list[: len(n_gpu)] + return device_list + + +def supervised_trainer_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 SupervisedTrainerMGPU(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..05885d98 --- /dev/null +++ b/models/pediatric_abdominal_ct_segmentation/scripts/prepare_datalist_monailabel.py @@ -0,0 +1,124 @@ +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 = 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: + 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")