From 5da898ff11e7693c612fd729347c06d6fe5bf52c Mon Sep 17 00:00:00 2001 From: jakob Date: Tue, 17 Dec 2024 10:23:13 +0100 Subject: [PATCH 1/5] remove p_tqdm dependency --- resources/train_nnunet.md | 2 +- setup.py | 1 - totalsegmentator/nnunet.py | 22 +++++++++++----------- totalsegmentator/statistics.py | 4 +--- 4 files changed, 13 insertions(+), 16 deletions(-) diff --git a/resources/train_nnunet.md b/resources/train_nnunet.md index 320d815fc..86962ece7 100644 --- a/resources/train_nnunet.md +++ b/resources/train_nnunet.md @@ -6,7 +6,7 @@ 4. Preprocess `nnUNetv2_plan_and_preprocess -d -pl ExperimentPlanner -c 3d_fullres -np 2` 5. Train `nnUNetv2_train 3d_fullres 0 -tr nnUNetTrainerNoMirroring` (takes several days) 6. Predict test set `nnUNetv2_predict -i path/to/imagesTs -o path/to/labelsTs_predicted -d -c 3d_fullres -tr nnUNetTrainerNoMirroring --disable_tta -f 0` -7. Evaluate `python resources/evaluate.py path/to/labelsTs path/to/labelsTs_predicted` (requires `pip install git+https://github.com/google-deepmind/surface-distance.git`). The resulting numbers should be similar to the ones in `resources/evaluate_results.txt` (since training is not deterministic the mean dice score across all classes can vary by up to one dice point) +7. Evaluate `python resources/evaluate.py path/to/labelsTs path/to/labelsTs_predicted` (requires `pip install git+https://github.com/google-deepmind/surface-distance.git` and `pip install p_tqdm`). The resulting numbers should be similar to the ones in `resources/evaluate_results.txt` (since training is not deterministic the mean dice score across all classes can vary by up to one dice point) 8. Done > Note: This will not give you the same results as TotalSegmentator for two reasons: diff --git a/setup.py b/setup.py index c0321a543..8cde4c563 100644 --- a/setup.py +++ b/setup.py @@ -22,7 +22,6 @@ 'SimpleITK', 'nibabel>=2.3.0', 'tqdm>=4.45.0', - 'p_tqdm', 'xvfbwrapper', 'nnunetv2>=2.2.1', 'requests==2.27.1;python_version<"3.10"', diff --git a/totalsegmentator/nnunet.py b/totalsegmentator/nnunet.py index 627f490c5..88f252821 100644 --- a/totalsegmentator/nnunet.py +++ b/totalsegmentator/nnunet.py @@ -18,7 +18,7 @@ import numpy as np import nibabel as nib from nibabel.nifti1 import Nifti1Image -from p_tqdm import p_map +# from p_tqdm import p_map import torch from totalsegmentator.libs import nostdout @@ -669,18 +669,18 @@ def nnUNet_predict_image(file_in: Union[str, Path, Nifti1Image], file_out, task_ # Code for multithreaded execution # Speed with different number of threads: # 1: 46s, 2: 24s, 6: 11s, 10: 8s, 14: 8s - nib.save(img_pred, tmp_dir / "s01.nii.gz") - _ = p_map(partial(save_segmentation_nifti, tmp_dir=tmp_dir, file_out=file_out, nora_tag=nora_tag, header=new_header, task_name=task_name, quiet=quiet), - selected_classes.items(), num_cpus=nr_threads_saving, disable=quiet) + # nib.save(img_pred, tmp_dir / "s01.nii.gz") + # _ = p_map(partial(save_segmentation_nifti, tmp_dir=tmp_dir, file_out=file_out, nora_tag=nora_tag, header=new_header, task_name=task_name, quiet=quiet), + # selected_classes.items(), num_cpus=nr_threads_saving, disable=quiet) # Multihreaded saving with same functions as in nnUNet -> same speed as p_map - # pool = Pool(nr_threads_saving) - # results = [] - # for k, v in selected_classes.items(): - # results.append(pool.starmap_async(save_segmentation_nifti, ((k, v, tmp_dir, file_out, nora_tag),) )) - # _ = [i.get() for i in results] # this actually starts the execution of the async functions - # pool.close() - # pool.join() + pool = Pool(nr_threads_saving) + results = [] + for k, v in selected_classes.items(): + results.append(pool.starmap_async(save_segmentation_nifti, [((k, v), tmp_dir, file_out, nora_tag, new_header, task_name, quiet)])) + _ = [i.get() for i in results] # this actually starts the execution of the async functions + pool.close() + pool.join() if not quiet: print(f" Saved in {time.time() - st:.2f}s") # Postprocessing single files diff --git a/totalsegmentator/statistics.py b/totalsegmentator/statistics.py index fda15e96d..5d435a9e9 100644 --- a/totalsegmentator/statistics.py +++ b/totalsegmentator/statistics.py @@ -10,7 +10,6 @@ import nibabel as nib from nibabel.nifti1 import Nifti1Image from tqdm import tqdm -from p_tqdm import p_map import numpy.ma as ma from totalsegmentator.map_to_binary import class_map @@ -58,8 +57,7 @@ def get_radiomics_features(seg_file, img_file="ct.nii.gz"): def get_radiomics_features_for_entire_dir(ct_file:Path, mask_dir:Path, file_out:Path): masks = sorted(list(mask_dir.glob("*.nii.gz"))) - stats = p_map(partial(get_radiomics_features, img_file=ct_file), - masks, num_cpus=1, disable=False) + stats = [get_radiomics_features(ct_file, mask) for mask in masks] stats = {mask_name: stats for mask_name, stats in stats} with open(file_out, "w") as f: json.dump(stats, f, indent=4) From 2370d9da02be556b18eee19cafd9b06698d639bc Mon Sep 17 00:00:00 2001 From: jakob Date: Tue, 17 Dec 2024 10:38:49 +0100 Subject: [PATCH 2/5] remove p_tqdm dependency; remove windows from tests --- .github/workflows/run_tests_os.yml | 4 ++-- setup.py | 1 - totalsegmentator/python_api.py | 6 ++++++ 3 files changed, 8 insertions(+), 3 deletions(-) diff --git a/.github/workflows/run_tests_os.yml b/.github/workflows/run_tests_os.yml index f7b7d6bb2..823016477 100644 --- a/.github/workflows/run_tests_os.yml +++ b/.github/workflows/run_tests_os.yml @@ -8,8 +8,8 @@ jobs: run-tests: strategy: matrix: - os: [ubuntu-latest, windows-latest, macos-latest] # fails on windows until https://github.com/MIC-DKFZ/nnUNet/issues/2396 is resolved - # os: [ubuntu-latest, macos-latest] + # os: [ubuntu-latest, windows-latest, macos-latest] # fails on windows until https://github.com/MIC-DKFZ/nnUNet/issues/2396 is resolved + os: [ubuntu-latest, macos-latest] python-version: ["3.10"] runs-on: ${{ matrix.os }} diff --git a/setup.py b/setup.py index 8cde4c563..aec779d6d 100644 --- a/setup.py +++ b/setup.py @@ -26,7 +26,6 @@ 'nnunetv2>=2.2.1', 'requests==2.27.1;python_version<"3.10"', 'requests;python_version>="3.10"', - 'rt_utils', 'dicom2nifti', 'pyarrow' ], diff --git a/totalsegmentator/python_api.py b/totalsegmentator/python_api.py index 74ff0c32a..5479c5089 100644 --- a/totalsegmentator/python_api.py +++ b/totalsegmentator/python_api.py @@ -94,6 +94,12 @@ def totalsegmentator(input: Union[str, Path, Nifti1Image], output: Union[str, Pa validate_device_type_api(device) device = convert_device_to_cuda(device) + if output_type == "dicom": + try: + from rt_utils import RTStructBuilder + except ImportError: + raise ImportError("rt_utils is required for output_type='dicom'. Please install it with 'pip install rt_utils'.") + # available devices: gpu | cpu | mps | gpu:1, gpu:2, etc. if device == "gpu": device = "cuda" From e9575c42808bbd0cf75038a6042f0306031de397 Mon Sep 17 00:00:00 2001 From: jakob Date: Tue, 17 Dec 2024 11:27:38 +0100 Subject: [PATCH 3/5] bugfix in multithreaded saving --- totalsegmentator/nnunet.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/totalsegmentator/nnunet.py b/totalsegmentator/nnunet.py index 88f252821..f4f446a1e 100644 --- a/totalsegmentator/nnunet.py +++ b/totalsegmentator/nnunet.py @@ -18,7 +18,7 @@ import numpy as np import nibabel as nib from nibabel.nifti1 import Nifti1Image -# from p_tqdm import p_map +from p_tqdm import p_map import torch from totalsegmentator.libs import nostdout @@ -666,10 +666,11 @@ def nnUNet_predict_image(file_in: Union[str, Path, Nifti1Image], file_out, task_ if nora_tag != "None": subprocess.call(f"/opt/nora/src/node/nora -p {nora_tag} --add {output_path} --addtag mask", shell=True) else: + nib.save(img_pred, tmp_dir / "s01.nii.gz") # needed inside of threads + # Code for multithreaded execution # Speed with different number of threads: # 1: 46s, 2: 24s, 6: 11s, 10: 8s, 14: 8s - # nib.save(img_pred, tmp_dir / "s01.nii.gz") # _ = p_map(partial(save_segmentation_nifti, tmp_dir=tmp_dir, file_out=file_out, nora_tag=nora_tag, header=new_header, task_name=task_name, quiet=quiet), # selected_classes.items(), num_cpus=nr_threads_saving, disable=quiet) From a0676aa6ae781e60e126e72d048f285d261e1c93 Mon Sep 17 00:00:00 2001 From: jakob Date: Tue, 17 Dec 2024 11:34:17 +0100 Subject: [PATCH 4/5] bugfix in multithreaded saving (2) --- totalsegmentator/nnunet.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/totalsegmentator/nnunet.py b/totalsegmentator/nnunet.py index f4f446a1e..2b1d12109 100644 --- a/totalsegmentator/nnunet.py +++ b/totalsegmentator/nnunet.py @@ -18,7 +18,7 @@ import numpy as np import nibabel as nib from nibabel.nifti1 import Nifti1Image -from p_tqdm import p_map +# from p_tqdm import p_map import torch from totalsegmentator.libs import nostdout From 777dbcc9dce60f3910eecba4e8eaa33b15f162bb Mon Sep 17 00:00:00 2001 From: jakob Date: Tue, 17 Dec 2024 12:56:16 +0100 Subject: [PATCH 5/5] update changelog --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 91e772eb7..89e00d8bf 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,8 @@ * add `totalseg_get_modality` * change pi_time threshold for arterial late phase from 50s to 60s * add oculomotor muscles model +* removed `rt_utils` and `p_tqdm` dependency + ## Release 2.4.0 * add brain structures