From acc25386e0c400c7dd882676d6e8fcb90bcdda5d Mon Sep 17 00:00:00 2001 From: mgam <312065559@qq.com> Date: Sat, 24 Jan 2026 14:35:55 +0800 Subject: [PATCH 1/7] refine logger level --- itkit/dataset/base.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/itkit/dataset/base.py b/itkit/dataset/base.py index 29d43dc..b9154b4 100644 --- a/itkit/dataset/base.py +++ b/itkit/dataset/base.py @@ -212,7 +212,7 @@ def _filter_by_meta(self, series_uids:list[str]): if dropped: print_log(f'Series Filter: Abandon {len(dropped)}/{len(series_uids)}。', MMLogger.get_current_instance(), logging.INFO) preview = '\n'.join([f' {u}: {r}' for u, r in dropped[:10]]) - print_log(f'示例(前10条):\n{preview}', MMLogger.get_current_instance(), logging.INFO) + print_log(f'示例(前10条):\n{preview}', MMLogger.get_current_instance(), logging.DEBUG) return kept From 0f03f969ae5ef245aa78a8efb776a505c01a7b75 Mon Sep 17 00:00:00 2001 From: mgam <312065559@qq.com> Date: Sat, 24 Jan 2026 14:36:46 +0800 Subject: [PATCH 2/7] Upgrade example configs --- examples/configs/0.0.AbdomenCT1K_TorchIO/SegFormer3D.py | 2 +- examples/configs/0.1.AbdomenCT1K_MONAI/SegFormer3D.py | 2 +- examples/configs/0.2.AbdomenCT1K_ITKIT/SegFormer3D.py | 2 +- examples/configs/1.0.TsdMRI_TorchIO/SegFormer3D.py | 2 +- examples/configs/1.0.TsdMRI_TorchIO/mgam.py | 2 +- examples/configs/1.1.TsdMRI_MONAI/SegFormer3D.py | 2 +- examples/configs/1.1.TsdMRI_MONAI/mgam.py | 2 +- examples/configs/1.2.TsdMRI_ITKIT/SegFormer3D.py | 2 +- examples/configs/1.2.TsdMRI_ITKIT/mgam.py | 2 +- pyproject.toml | 2 +- 10 files changed, 10 insertions(+), 10 deletions(-) diff --git a/examples/configs/0.0.AbdomenCT1K_TorchIO/SegFormer3D.py b/examples/configs/0.0.AbdomenCT1K_TorchIO/SegFormer3D.py index f327b7d..2fa14e0 100644 --- a/examples/configs/0.0.AbdomenCT1K_TorchIO/SegFormer3D.py +++ b/examples/configs/0.0.AbdomenCT1K_TorchIO/SegFormer3D.py @@ -14,7 +14,7 @@ type=SegFormer3D, in_channels=in_channels, # pyright: ignore num_classes=num_classes, - sr_ratios= [(4,8,8), (2,4,4), (2,2,2), (1,1,1)], + sr_ratios= [4, 2, 2, 1 ], patch_kernel_size= [7, 3, 3, 3 ], patch_stride= [4, 2, 2, 2 ], patch_padding= [3, 1, 1, 1 ], diff --git a/examples/configs/0.1.AbdomenCT1K_MONAI/SegFormer3D.py b/examples/configs/0.1.AbdomenCT1K_MONAI/SegFormer3D.py index f327b7d..2fa14e0 100644 --- a/examples/configs/0.1.AbdomenCT1K_MONAI/SegFormer3D.py +++ b/examples/configs/0.1.AbdomenCT1K_MONAI/SegFormer3D.py @@ -14,7 +14,7 @@ type=SegFormer3D, in_channels=in_channels, # pyright: ignore num_classes=num_classes, - sr_ratios= [(4,8,8), (2,4,4), (2,2,2), (1,1,1)], + sr_ratios= [4, 2, 2, 1 ], patch_kernel_size= [7, 3, 3, 3 ], patch_stride= [4, 2, 2, 2 ], patch_padding= [3, 1, 1, 1 ], diff --git a/examples/configs/0.2.AbdomenCT1K_ITKIT/SegFormer3D.py b/examples/configs/0.2.AbdomenCT1K_ITKIT/SegFormer3D.py index f327b7d..2fa14e0 100644 --- a/examples/configs/0.2.AbdomenCT1K_ITKIT/SegFormer3D.py +++ b/examples/configs/0.2.AbdomenCT1K_ITKIT/SegFormer3D.py @@ -14,7 +14,7 @@ type=SegFormer3D, in_channels=in_channels, # pyright: ignore num_classes=num_classes, - sr_ratios= [(4,8,8), (2,4,4), (2,2,2), (1,1,1)], + sr_ratios= [4, 2, 2, 1 ], patch_kernel_size= [7, 3, 3, 3 ], patch_stride= [4, 2, 2, 2 ], patch_padding= [3, 1, 1, 1 ], diff --git a/examples/configs/1.0.TsdMRI_TorchIO/SegFormer3D.py b/examples/configs/1.0.TsdMRI_TorchIO/SegFormer3D.py index f327b7d..2fa14e0 100644 --- a/examples/configs/1.0.TsdMRI_TorchIO/SegFormer3D.py +++ b/examples/configs/1.0.TsdMRI_TorchIO/SegFormer3D.py @@ -14,7 +14,7 @@ type=SegFormer3D, in_channels=in_channels, # pyright: ignore num_classes=num_classes, - sr_ratios= [(4,8,8), (2,4,4), (2,2,2), (1,1,1)], + sr_ratios= [4, 2, 2, 1 ], patch_kernel_size= [7, 3, 3, 3 ], patch_stride= [4, 2, 2, 2 ], patch_padding= [3, 1, 1, 1 ], diff --git a/examples/configs/1.0.TsdMRI_TorchIO/mgam.py b/examples/configs/1.0.TsdMRI_TorchIO/mgam.py index 67bcb55..6d1941f 100644 --- a/examples/configs/1.0.TsdMRI_TorchIO/mgam.py +++ b/examples/configs/1.0.TsdMRI_TorchIO/mgam.py @@ -69,7 +69,7 @@ num_classes = 57 wl = 100 # window level ww = 300 # window width -size = (32,32,32) # [Z, Y, X] +size = (32,96,96) # [Z, Y, X] pad_val = 0 seg_pad_val = 0 diff --git a/examples/configs/1.1.TsdMRI_MONAI/SegFormer3D.py b/examples/configs/1.1.TsdMRI_MONAI/SegFormer3D.py index f327b7d..2fa14e0 100644 --- a/examples/configs/1.1.TsdMRI_MONAI/SegFormer3D.py +++ b/examples/configs/1.1.TsdMRI_MONAI/SegFormer3D.py @@ -14,7 +14,7 @@ type=SegFormer3D, in_channels=in_channels, # pyright: ignore num_classes=num_classes, - sr_ratios= [(4,8,8), (2,4,4), (2,2,2), (1,1,1)], + sr_ratios= [4, 2, 2, 1 ], patch_kernel_size= [7, 3, 3, 3 ], patch_stride= [4, 2, 2, 2 ], patch_padding= [3, 1, 1, 1 ], diff --git a/examples/configs/1.1.TsdMRI_MONAI/mgam.py b/examples/configs/1.1.TsdMRI_MONAI/mgam.py index 81e3f34..2d017d3 100644 --- a/examples/configs/1.1.TsdMRI_MONAI/mgam.py +++ b/examples/configs/1.1.TsdMRI_MONAI/mgam.py @@ -69,7 +69,7 @@ num_classes = 57 wl = 100 # window level ww = 300 # window width -size = (32,32,32) # [Z, Y, X] +size = (32,96,96) # [Z, Y, X] pad_val = 0 seg_pad_val = 0 diff --git a/examples/configs/1.2.TsdMRI_ITKIT/SegFormer3D.py b/examples/configs/1.2.TsdMRI_ITKIT/SegFormer3D.py index f327b7d..2fa14e0 100644 --- a/examples/configs/1.2.TsdMRI_ITKIT/SegFormer3D.py +++ b/examples/configs/1.2.TsdMRI_ITKIT/SegFormer3D.py @@ -14,7 +14,7 @@ type=SegFormer3D, in_channels=in_channels, # pyright: ignore num_classes=num_classes, - sr_ratios= [(4,8,8), (2,4,4), (2,2,2), (1,1,1)], + sr_ratios= [4, 2, 2, 1 ], patch_kernel_size= [7, 3, 3, 3 ], patch_stride= [4, 2, 2, 2 ], patch_padding= [3, 1, 1, 1 ], diff --git a/examples/configs/1.2.TsdMRI_ITKIT/mgam.py b/examples/configs/1.2.TsdMRI_ITKIT/mgam.py index d595ef7..28a4673 100644 --- a/examples/configs/1.2.TsdMRI_ITKIT/mgam.py +++ b/examples/configs/1.2.TsdMRI_ITKIT/mgam.py @@ -68,7 +68,7 @@ num_classes = 57 wl = 100 # window level ww = 300 # window width -size = (32,32,32) # [Z, Y, X] +size = (32,96,96) # [Z, Y, X] pad_val = 0 seg_pad_val = 0 diff --git a/pyproject.toml b/pyproject.toml index 19c8151..7ddbfe3 100755 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "itkit" -version = "4.0.0rc2" +version = "4.0.0rc3" requires-python = ">= 3.10" description = "ITKIT: Feasible Medical Image Operation based on SimpleITK API" readme = "README.md" From 97e89c44051e76e1497eaf258cad763d06ed691b Mon Sep 17 00:00:00 2001 From: mgam <312065559@qq.com> Date: Sat, 24 Jan 2026 14:41:11 +0800 Subject: [PATCH 3/7] Upgrade example configs --- examples/configs/0.0.AbdomenCT1K_TorchIO/mgam.py | 2 +- examples/configs/0.1.AbdomenCT1K_MONAI/mgam.py | 2 +- examples/configs/0.2.AbdomenCT1K_ITKIT/mgam.py | 2 +- examples/configs/1.0.TsdMRI_TorchIO/mgam.py | 4 ++-- examples/configs/1.1.TsdMRI_MONAI/mgam.py | 4 ++-- examples/configs/1.2.TsdMRI_ITKIT/mgam.py | 4 ++-- 6 files changed, 9 insertions(+), 9 deletions(-) diff --git a/examples/configs/0.0.AbdomenCT1K_TorchIO/mgam.py b/examples/configs/0.0.AbdomenCT1K_TorchIO/mgam.py index d599064..ae4fce1 100644 --- a/examples/configs/0.0.AbdomenCT1K_TorchIO/mgam.py +++ b/examples/configs/0.0.AbdomenCT1K_TorchIO/mgam.py @@ -68,7 +68,7 @@ data_root = "/mnt/wsl/Fwsldatavhdx/mgam_datasets/AbdomenCT_1K/original" num_classes = 5 wl = 50 # window level -ww = 300 # window width +ww = 500 # window width size = (96,96,96) # [Z, Y, X] pad_val = 0 seg_pad_val = 0 diff --git a/examples/configs/0.1.AbdomenCT1K_MONAI/mgam.py b/examples/configs/0.1.AbdomenCT1K_MONAI/mgam.py index 49160ac..43e5e54 100644 --- a/examples/configs/0.1.AbdomenCT1K_MONAI/mgam.py +++ b/examples/configs/0.1.AbdomenCT1K_MONAI/mgam.py @@ -68,7 +68,7 @@ data_root = "/mnt/wsl/Fwsldatavhdx/mgam_datasets/AbdomenCT_1K/original" num_classes = 5 wl = 50 # window level -ww = 300 # window width +ww = 500 # window width size = (96,96,96) # [Z, Y, X] pad_val = 0 seg_pad_val = 0 diff --git a/examples/configs/0.2.AbdomenCT1K_ITKIT/mgam.py b/examples/configs/0.2.AbdomenCT1K_ITKIT/mgam.py index ac67d8a..e466d64 100644 --- a/examples/configs/0.2.AbdomenCT1K_ITKIT/mgam.py +++ b/examples/configs/0.2.AbdomenCT1K_ITKIT/mgam.py @@ -67,7 +67,7 @@ data_root = "/mnt/wsl/Fwsldatavhdx/mgam_datasets/AbdomenCT_1K/original" num_classes = 5 wl = 50 # window level -ww = 300 # window width +ww = 500 # window width size = (96,96,96) # [Z, Y, X] pad_val = 0 seg_pad_val = 0 diff --git a/examples/configs/1.0.TsdMRI_TorchIO/mgam.py b/examples/configs/1.0.TsdMRI_TorchIO/mgam.py index 6d1941f..1caebca 100644 --- a/examples/configs/1.0.TsdMRI_TorchIO/mgam.py +++ b/examples/configs/1.0.TsdMRI_TorchIO/mgam.py @@ -67,8 +67,8 @@ # Dataset data_root = "/mnt/wsl/Fwsldatavhdx/mgam_datasets/TotalSegmentatorMRI/spacing2" num_classes = 57 -wl = 100 # window level -ww = 300 # window width +wl = 200 # window level +ww = 400 # window width size = (32,96,96) # [Z, Y, X] pad_val = 0 seg_pad_val = 0 diff --git a/examples/configs/1.1.TsdMRI_MONAI/mgam.py b/examples/configs/1.1.TsdMRI_MONAI/mgam.py index 2d017d3..f2acc9b 100644 --- a/examples/configs/1.1.TsdMRI_MONAI/mgam.py +++ b/examples/configs/1.1.TsdMRI_MONAI/mgam.py @@ -67,8 +67,8 @@ # Dataset data_root = "/mnt/wsl/Fwsldatavhdx/mgam_datasets/TotalSegmentatorMRI/spacing2" num_classes = 57 -wl = 100 # window level -ww = 300 # window width +wl = 200 # window level +ww = 400 # window width size = (32,96,96) # [Z, Y, X] pad_val = 0 seg_pad_val = 0 diff --git a/examples/configs/1.2.TsdMRI_ITKIT/mgam.py b/examples/configs/1.2.TsdMRI_ITKIT/mgam.py index 28a4673..e1adeda 100644 --- a/examples/configs/1.2.TsdMRI_ITKIT/mgam.py +++ b/examples/configs/1.2.TsdMRI_ITKIT/mgam.py @@ -66,8 +66,8 @@ # Dataset data_root = "/mnt/wsl/Fwsldatavhdx/mgam_datasets/TotalSegmentatorMRI/spacing2" num_classes = 57 -wl = 100 # window level -ww = 300 # window width +wl = 200 # window level +ww = 400 # window width size = (32,96,96) # [Z, Y, X] pad_val = 0 seg_pad_val = 0 From 5227bdea530768368ae38fc40e89a5a4c586a0f0 Mon Sep 17 00:00:00 2001 From: mgam <312065559@qq.com> Date: Sat, 24 Jan 2026 22:04:24 +0800 Subject: [PATCH 4/7] remove useless notebooks --- .../filter_itk_patched_only_background.ipynb | 72 ------------------- 1 file changed, 72 deletions(-) delete mode 100644 notebooks/filter_itk_patched_only_background.ipynb diff --git a/notebooks/filter_itk_patched_only_background.ipynb b/notebooks/filter_itk_patched_only_background.ipynb deleted file mode 100644 index fd18277..0000000 --- a/notebooks/filter_itk_patched_only_background.ipynb +++ /dev/null @@ -1,72 +0,0 @@ -{ - "cells": [ - { - "cell_type": "markdown", - "id": "7fa9319c", - "metadata": {}, - "source": [ - "Filter the patched data sample generated by `itk_patch` command to prevent too much pure-background patches." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "958c9fea", - "metadata": {}, - "outputs": [], - "source": [ - "import os, json\n", - "from pathlib import Path\n", - "from random import random\n", - "\n", - "def parse_patch_meta(folder: Path):\n", - " meta = json.load(open(folder / \"crop_meta.json\"))[\"patch_meta\"]\n", - " for seriesUID, series_meta in meta.items():\n", - " for patched_sample_name, cls_in_patch in series_meta[\"class_within_patch\"].items():\n", - " if cls_in_patch == [0]:\n", - " yield patched_sample_name\n", - "\n", - "def delete_pure_background_patches(folder: Path, prob_to_still_keep):\n", - " for i, patched_sample_name in enumerate(parse_patch_meta(folder)):\n", - " if random() > prob_to_still_keep:\n", - " os.remove(folder / 'image' / patched_sample_name)\n", - " os.remove(folder / 'label' / patched_sample_name)\n", - " print(f\"Deleted {i}: {patched_sample_name}\")" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "b37e5e9a", - "metadata": {}, - "outputs": [], - "source": [ - "delete_pure_background_patches(\n", - " folder = Path(\"/mnt/wsl/Fwsldatavhdx/mgam_datasets/LiTS/spacing1-0.6-0.6/patch80\"),\n", - " prob_to_still_keep = 0.1,\n", - ")" - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "pt2508", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.13.8" - } - }, - "nbformat": 4, - "nbformat_minor": 5 -} From 285fcac6658a2397a24728ed6f2fddf0059e80e5 Mon Sep 17 00:00:00 2001 From: mgam <312065559@qq.com> Date: Sat, 24 Jan 2026 22:31:56 +0800 Subject: [PATCH 5/7] refine docs --- docs/contributing.md | 182 +------------------ docs/datasets.md | 67 +------ docs/index.md | 31 ---- docs/installation.md | 22 --- docs/models.md | 2 +- docs/preprocessing.md | 19 -- docs/quickstart.md | 203 --------------------- docs/slicer_integration.md | 363 +------------------------------------ mkdocs.yml | 2 - 9 files changed, 9 insertions(+), 882 deletions(-) delete mode 100644 docs/preprocessing.md delete mode 100644 docs/quickstart.md diff --git a/docs/contributing.md b/docs/contributing.md index 1eb5c0a..c1e4e0e 100644 --- a/docs/contributing.md +++ b/docs/contributing.md @@ -35,130 +35,6 @@ Thank you for your interest in contributing to ITKIT! This guide will help you g This ensures code is automatically formatted and checked before commits. -## Development Workflow - -### Code Style - -ITKIT follows these code style guidelines: - -- **Python version:** >= 3.10 -- **Formatter:** Black with 150 character line length -- **Import sorting:** isort with black profile -- **Type hints:** Encouraged for public APIs -- **Docstrings:** Follow NumPy/Google style - -### Running Tests - -ITKIT uses pytest for testing: - -```bash -# Run all tests -pytest - -# Run specific test file -pytest tests/test_io.py - -# Run tests with specific marker -pytest -m torch # Only PyTorch tests -pytest -m itk_process # Only ITK processing tests - -# Run with coverage -pytest --cov=itkit -``` - -Available test markers: - -- `gui`: GUI tests (requires Qt) -- `itk_process`: ITK preprocessing script tests -- `mm`: MMEngine runner tests -- `torch`: PyTorch-dependent tests - -### Code Formatting - -Format your code before committing: - -```bash -# Format with black -black itkit/ - -# Sort imports -isort itkit/ - -# Or use pre-commit to run all checks -pre-commit run --all-files -``` - -### Type Checking - -Run type checkers: - -```bash -# Pyright -pyright - -# Mypy -mypy itkit/ -``` - -## Contribution Guidelines - -### Reporting Issues - -When reporting bugs or requesting features: - -1. **Search existing issues** first to avoid duplicates -2. **Use issue templates** if available -3. **Provide clear description** with: - - Expected behavior - - Actual behavior - - Steps to reproduce - - Environment details (OS, Python version, ITKIT version) - - Sample code or data if applicable - -### Submitting Pull Requests - -1. **Create a new branch** for your feature/fix: - - ```bash - git checkout -b feature/my-new-feature - ``` - -2. **Make your changes** following code style guidelines - -3. **Add tests** for new functionality - -4. **Update documentation** if needed - -5. **Run tests** to ensure nothing breaks: - - ```bash - pytest - ``` - -6. **Commit your changes** with clear messages: - - ```bash - git commit -m "Add feature: description of feature" - ``` - -7. **Push to your fork:** - - ```bash - git push origin feature/my-new-feature - ``` - -8. **Create a Pull Request** on GitHub - -### Pull Request Checklist - -- [ ] Code follows ITKIT style guidelines -- [ ] Tests added for new functionality -- [ ] All tests pass -- [ ] Documentation updated -- [ ] Commit messages are clear -- [ ] PR description explains changes -- [ ] No unnecessary files included - ## What to Contribute ### Areas for Contribution @@ -249,64 +125,8 @@ To add a new preprocessing command: 4. Add tests in `tests/` 5. Support common flags (`--mp`, `--help`) -## Release Process - -ITKIT follows a specific release policy: - -### Release Branches - -- Stable releases managed on branches: `v1`, `v2`, `v3`, etc. -- Development occurs on feature branches -- Only merges to release branches trigger releases - -### Release Triggers - -- Any PR merged into a release branch automatically triggers a new release -- Releases are typically minor version updates (v3.1, v3.2, etc.) -- Tagged accordingly - -### Version Numbering - -ITKIT uses semantic versioning: `MAJOR.MINOR.PATCH` - -- **MAJOR:** Breaking changes -- **MINOR:** New features (backwards compatible) -- **PATCH:** Bug fixes (backwards compatible) - -## Communication - -### Getting Help +## Getting Help - **Issues:** Use GitHub Issues for bug reports and feature requests - **Email:** Contact maintainer at [312065559@qq.com](mailto:312065559@qq.com) - **Discussions:** Use GitHub Discussions for questions - -### Community Guidelines - -- Be respectful and constructive -- Help others when you can -- Follow the code of conduct -- Give credit where due -- Focus on what's best for the project - -## License - -By contributing to ITKIT, you agree that your contributions will be licensed under the MIT License. - -## Recognition - -Contributors are recognized in: - -- GitHub contributors list -- Release notes -- CITATION file (for significant contributions) - -## Questions? - -If you have any questions about contributing, feel free to: - -- Open an issue with the "question" label -- Contact the maintainer via email -- Start a discussion on GitHub - -Thank you for making ITKIT better! diff --git a/docs/datasets.md b/docs/datasets.md index 8a02648..38b452c 100644 --- a/docs/datasets.md +++ b/docs/datasets.md @@ -209,68 +209,7 @@ For each supported dataset, you can find conversion scripts at: `itkit/dataset/< --- -## Dataset Categories - -### By Modality - -**CT Datasets:** - -- AbdomenCT-1K -- CT-ORG -- CTSpine1K -- FLARE 2022/2023 -- ImageTBAD -- KiTS23 -- LUNA16 -- TotalSegmentator -- LiTS - -**MRI Datasets:** - -- BraTS 2024 - -**Multi-Modal:** - -- TCGA - -### By Anatomical Region - -**Abdomen:** - -- AbdomenCT-1K -- FLARE 2022/2023 -- LiTS - -**Brain:** - -- BraTS 2024 -- CT-ORG (brain) - -**Thorax:** - -- LUNA16 -- CT-ORG (lungs) - -**Kidney:** - -- KiTS23 -- CT-ORG (kidneys) - -**Cardiovascular:** - -- ImageTBAD - -**Spine:** - -- CTSpine1K - -**Whole Body:** - -- TotalSegmentator - -## Using Conversion Scripts - -### Example: Converting AbdomenCT-1K +## Example: Converting AbdomenCT-1K ```bash # Navigate to dataset conversion directory @@ -282,12 +221,12 @@ python convert_official.py \ --output /path/to/itkit/format ``` -### General Workflow +## General Workflow 1. **Download official dataset** from the source 2. **Locate conversion script** in `itkit/dataset//` 3. **Run conversion script** with appropriate paths -4. **Verify structure** matches ITKIT format (image/ and label/ folders) +4. **Verify structure** matches ITKIT format (`image/` and `label/` folders) 5. **Use with ITKIT tools** for preprocessing and training ## Custom Dataset Preparation diff --git a/docs/index.md b/docs/index.md index 54cbe62..21588f9 100644 --- a/docs/index.md +++ b/docs/index.md @@ -9,12 +9,10 @@ Welcome to the ITKIT documentation! ITKIT is a user-friendly toolkit built on `S ### Getting Started - **[Installation Guide](installation.md)** - Install ITKIT and its dependencies -- **[Quick Start](quickstart.md)** - Get started with basic usage and examples - **[Dataset Structure](dataset_structure.md)** - Understand the required dataset format ### Processing Tools -- **[Overview](preprocessing.md)** - General notes and best practices - **[itk_check](itk_check.md)** - Image checking and validation - **[itk_resample](itk_resample.md)** - Resampling to target spacing/size - **[itk_orient](itk_orient.md)** - Image re-orientation @@ -57,12 +55,6 @@ Welcome to the ITKIT documentation! ITKIT is a user-friendly toolkit built on `S - Submission process - Release policy -- **[FAQ & Troubleshooting](faq.md)** - Common issues and solutions - - Installation problems - - Usage questions - - Performance tips - - Framework integration issues - ## 🚀 Key Features - **🔧 Feasible Operations**: Simple command-line interface for complex ITK operations @@ -73,29 +65,6 @@ Welcome to the ITKIT documentation! ITKIT is a user-friendly toolkit built on `S - **⚡ High Performance**: Multiprocessing support for faster preprocessing - **🎨 Flexible**: Works with multiple file formats (MHA, NIfTI, NRRD, DICOM) -## 🎯 Quick Navigation - -**New to ITKIT?** - -1. Start with [Installation Guide](installation.md) -2. Follow the [Quick Start](quickstart.md) tutorial -3. Learn about [Dataset Structure](dataset_structure.md) - -**Ready to preprocess data?** - -- See [Processing Tools](preprocessing.md) for command documentation and overview. -- Use the GUI: `pip install "itkit[gui]"` then `itkit-app` - -**Building models?** - -- Explore [Framework Integration](framework_integration.md) -- Check available [Models](models.md) - -**Need help?** - -- Check [FAQ & Troubleshooting](faq.md) -- See [Contributing Guide](contributing.md) to report issues - ## 📝 Citation If you use ITKIT in your research, please cite: diff --git a/docs/installation.md b/docs/installation.md index ab2c920..0bdbf8e 100644 --- a/docs/installation.md +++ b/docs/installation.md @@ -129,25 +129,3 @@ python -c "import itkit; print(itkit.__version__)" itk_check --help itk_resample --help ``` - -## Troubleshooting - -### Import Errors - -If you encounter import errors, ensure all dependencies are installed: - -```bash -pip install itkit --force-reinstall -``` - -### GUI DPI Issues - -If the GUI's DPI is not optimal, specify the `QT_SCALE_FACTOR` environment variable: - -```bash -QT_SCALE_FACTOR=2 itkit-app -``` - -### PyTorch Compatibility - -For GPU support with PyTorch-based features, install PyTorch separately following the [official PyTorch installation guide](https://pytorch.org/get-started/locally/). diff --git a/docs/models.md b/docs/models.md index 999ede2..67feb40 100644 --- a/docs/models.md +++ b/docs/models.md @@ -183,7 +183,7 @@ from itkit.models import DA_TransUNet # Model config in your experiment config file model = dict( - type='DA_TransUNet', + type=DA_TransUNet, in_channels=1, num_classes=3, # ... other parameters diff --git a/docs/preprocessing.md b/docs/preprocessing.md deleted file mode 100644 index 6599ca4..0000000 --- a/docs/preprocessing.md +++ /dev/null @@ -1,19 +0,0 @@ -# ITK Preprocessing Tools - -ITKIT provides comprehensive command-line tools for medical image preprocessing. Each tool is designed for a specific operation and follows a consistent interface pattern. - -## General Notes - -- **Coordinate Order**: All dimension arguments use **Z, Y, X** order (Z→0, Y→1, X→2) -- **Help**: Use '--help' with any command to see detailed usage information -- **Multiprocessing**: Most commands support '--mp' flag for parallel processing -- **Progress**: Commands display progress bars using tqdm - -## Best Practices - -1. **Use multiprocessing**: Add '--mp' flag for large datasets to speed up processing -2. **Check before processing**: Always run 'itk_check' before other operations -3. **Preserve originals**: Work on copies of your data, never modify originals -4. **Pipeline operations**: Chain commands to create preprocessing pipelines -5. **Validate outputs**: Check a few samples manually after each processing step -6. **Use consistent spacing**: Resample all data to the same spacing for training diff --git a/docs/quickstart.md b/docs/quickstart.md deleted file mode 100644 index 1e6c865..0000000 --- a/docs/quickstart.md +++ /dev/null @@ -1,203 +0,0 @@ -# Quick Start Guide - -This guide will help you get started with ITKIT's core functionality. - -## Basic Usage - -### Command-Line Interface - -ITKIT provides several command-line tools for medical image preprocessing. All commands follow a consistent interface pattern. - -To see available options for any command, use the `--help` flag: - -```bash -itk_check --help -itk_resample --help -itk_orient --help -``` - -### Simple Example: Checking Dataset - -Check if your dataset meets specific spacing and size requirements: - -```bash -itk_check check /path/to/dataset \ - --min-spacing 0.5 0.5 0.5 \ - --max-spacing 2.0 2.0 2.0 -``` - -### Simple Example: Resampling - -Resample images to a target spacing: - -```bash -itk_resample dataset /path/to/source /path/to/destination \ - --spacing 1.0 1.0 1.0 -``` - -### Simple Example: Orientation - -Orient images to a standard orientation (e.g., LPI): - -```bash -itk_orient /path/to/source /path/to/destination LPI -``` - -## Using the GUI Application - -ITKIT provides a PyQt6-based graphical user interface for all preprocessing operations. - -### Installing GUI Support - -First, install ITKIT with GUI support: - -```bash -pip install "itkit[gui]" -``` - -### Launching the GUI - -```bash -itkit-app -``` - -### Adjusting GUI DPI - -If the GUI's DPI is not optimal, specify the `QT_SCALE_FACTOR` environment variable: - -```bash -QT_SCALE_FACTOR=2 itkit-app -``` - -![ITKIT GUI](itkit-gui.png) - -The GUI provides an intuitive interface for: - -- Checking dataset integrity -- Resampling images -- Orienting images -- Extracting patches -- Data augmentation -- Label extraction -- Format conversion - -## Python API Usage - -You can also use ITKIT programmatically in your Python scripts. - -### Reading and Writing Images - -```python -from itkit.io import sitk_toolkit as sitk_io - -# Read an image -image = sitk_io.read_image("/path/to/image.mha") - -# Get image properties -spacing = sitk_io.get_spacing(image) -size = sitk_io.get_size(image) -origin = sitk_io.get_origin(image) - -# Write an image -sitk_io.write_image(image, "/path/to/output.mha") -``` - -### Working with DICOM - -```python -from itkit.io import dcm_toolkit as dcm_io - -# Read DICOM series -image = dcm_io.read_dicom_series("/path/to/dicom/folder") - -# Convert to other format -dcm_io.write_image(image, "/path/to/output.nii.gz") -``` - -### Dataset Operations - -```python -from itkit.dataset import ITKITBaseSegDataset - -# Create dataset instance -dataset = ITKITBaseSegDataset( - root_dir="/path/to/dataset", - mode="train" -) - -# Access samples -image, label = dataset[0] -``` - -## Common Workflows - -### Workflow 1: Preparing Data for Training - -1. **Check dataset integrity:** - - ```bash - itk_check check /data/raw_dataset --min-size 32 32 32 - ``` - -2. **Resample to uniform spacing:** - - ```bash - itk_resample dataset /data/raw_dataset /data/resampled \ - --spacing 1.0 1.0 1.0 --mp - ``` - -3. **Orient to standard direction:** - - ```bash - itk_orient /data/resampled /data/oriented LPI --mp - ``` - -4. **Extract patches for training:** - - ```bash - itk_patch /data/oriented /data/patches \ - --patch-size 96 96 96 \ - --patch-stride 48 48 48 \ - --minimum-foreground-ratio 0.1 \ - --mp - ``` - -### Workflow 2: Converting Dataset Format - -1. **Convert to MONAI format:** - - ```bash - itk_convert monai /data/itkit_dataset /data/monai_dataset \ - --name MyDataset \ - --modality CT \ - --labels background liver tumor \ - --mp - ``` - -2. **Convert file format:** - - ```bash - itk_convert format nii.gz /data/mha_dataset /data/nifti_dataset \ - --mp --workers 8 - ``` - -### Workflow 3: Data Augmentation - -Generate augmented samples with random rotations: - -```bash -itk_aug /data/images /data/labels \ - -oimg /data/aug_images \ - -olbl /data/aug_labels \ - -n 5 \ - --random-rot 15 15 15 \ - --mp -``` - -## Multiprocessing Support - -Most ITKIT commands support multiprocessing with the `--mp` flag for faster processing: - -```bash -itk_resample dataset /src /dst --spacing 1.0 1.0 1.0 --mp --workers 8 -``` diff --git a/docs/slicer_integration.md b/docs/slicer_integration.md index 7c9b9bc..805c8ad 100644 --- a/docs/slicer_integration.md +++ b/docs/slicer_integration.md @@ -2,40 +2,6 @@ ITKIT provides a native 3D Slicer extension for running inference directly within the Slicer environment. This integration allows radiologists and researchers to seamlessly apply trained segmentation models to medical images without leaving the Slicer interface. -## Overview - -The ITKIT Slicer extension (SlicerITKIT) provides a user-friendly interface for: - -- Loading and configuring deep learning models -- Running inference on 3D medical volumes -- Generating segmentation results as Slicer segmentation nodes -- Configuring advanced inference parameters (sliding window, precision, etc.) - -## Features - -### Multiple Backend Support - -- **MMEngine Backend**: Full PyTorch model support with configuration files -- **ONNX Backend**: Optimized inference with ONNX Runtime - -### Sliding Window Inference - -- Handle large volumes that don't fit in GPU memory -- Configurable patch size and stride for optimal results -- Automatic padding and cropping - -### GPU Acceleration - -- CUDA support for fast inference -- Mixed precision (FP16) support -- CPU fallback for systems without GPU - -### Seamless Integration - -- Works directly with Slicer volume nodes -- Outputs standard Slicer segmentation nodes -- Compatible with Slicer's segment editor and visualization tools - ## Installation See the [Installation Guide](../SlicerITKIT/INSTALLATION.md) for detailed instructions. @@ -49,342 +15,21 @@ Quick summary: ## Usage -### Basic Workflow - -1. **Load Your Data** - - ```python - # Load a volume in Slicer (via GUI or Python) - import slicer - volumeNode = slicer.util.loadVolume('/path/to/your/image.nii.gz') - ``` - -2. **Open ITKIT Inference Module** +1. **Open ITKIT Inference Module** - Navigate to: Modules → Segmentation → ITKIT Inference -3. **Configure Backend** +2. **Configure Backend** - Select backend type (MMEngine or ONNX) - Browse for configuration file (MMEngine only) - Browse for checkpoint/model file -4. **Set Parameters** (optional) +3. **Set Parameters** (optional) - Patch size for sliding window (e.g., `96,96,96`) - Patch stride (e.g., `48,48,48`) - Enable FP16 for faster inference - Force CPU accumulation if needed -5. **Run Inference** +4. **Run Inference** - Click "Run Inference" - Monitor progress - View results in segmentation node - -### Example: Abdominal CT Segmentation - -```python -# Example using Python scripting in Slicer -import slicer - -# Load CT scan -volumeNode = slicer.util.loadVolume('/path/to/abdomen_ct.nii.gz') - -# Get the ITKIT Inference logic -logic = slicer.modules.itkitinference.widgetRepresentation().self().logic - -# Run inference -segmentation = logic.process( - inputVolume=volumeNode, - outputSegmentation=None, - backend="MMEngine", - configPath="/path/to/config.py", - checkpointPath="/path/to/checkpoint.pth", - patchSize=(128, 128, 128), - patchStride=(64, 64, 64), - fp16=True, - forceCpu=False -) - -# Segmentation is now available for visualization and editing -``` - -## Model Configuration - -### MMEngine Models - -Your model configuration should be compatible with ITKIT's MMEngine integration: - -```python -# config.py -model = dict( - type='YourSegmentationModel', - backbone=dict( - type='YourBackbone', - in_channels=1, - # ... backbone config - ), - decode_head=dict( - type='YourDecodeHead', - num_classes=5, # Number of segmentation classes - # ... head config - ), - # Optional: default inference configuration - inference_config=dict( - patch_size=(96, 96, 96), - patch_stride=(48, 48, 48), - accumulate_device='cuda', - forward_device='cuda', - ) -) -``` - -### ONNX Models - -Export your trained model to ONNX format: - -```python -import torch -from itkit.mm.inference import MMEngineInferBackend - -# Load your trained model -backend = MMEngineInferBackend( - cfg_path='path/to/config.py', - ckpt_path='path/to/checkpoint.pth' -) - -# Create dummy input matching your model's expected input shape -dummy_input = torch.randn(1, 1, 96, 96, 96).cuda() - -# Export to ONNX -# Important: The model component to export depends on your specific architecture -# Common patterns: -# 1. Models with separate backbone: backend.model.backbone (e.g., SegFormer) -# 2. Unified models without backbone: backend.model (e.g., simple U-Net variants) -# 3. Models with encode-decode structure: backend.model.encode_decode -# Check your model's architecture to determine the correct component to export -torch.onnx.export( - backend.model.backbone, # Adjust based on your model architecture - dummy_input, - 'model.onnx', - input_names=['input'], - output_names=['output'], - dynamic_axes={ - 'input': {0: 'batch', 2: 'depth', 3: 'height', 4: 'width'}, - 'output': {0: 'batch', 2: 'depth', 3: 'height', 4: 'width'} - }, - opset_version=11 -) -``` - -## Advanced Usage - -### Scripting - -You can automate inference for batch processing: - -```python -import os -import slicer - -# Get logic -logic = slicer.modules.itkitinference.widgetRepresentation().self().logic - -# Process multiple volumes -input_dir = '/path/to/input/volumes' -output_dir = '/path/to/output/segmentations' - -for filename in os.listdir(input_dir): - if filename.endswith('.nii.gz'): - # Load volume - filepath = os.path.join(input_dir, filename) - volumeNode = slicer.util.loadVolume(filepath) - - # Run inference - segmentation = logic.process( - inputVolume=volumeNode, - outputSegmentation=None, - backend="ONNX", - configPath=None, - checkpointPath="/path/to/model.onnx", - patchSize=None, # Use full volume - patchStride=None, - fp16=False, - forceCpu=False - ) - - # Save segmentation - output_path = os.path.join(output_dir, filename.replace('.nii.gz', '_seg.nii.gz')) - slicer.util.saveNode(segmentation, output_path) - - # Clean up - slicer.mrmlScene.RemoveNode(volumeNode) - slicer.mrmlScene.RemoveNode(segmentation) - -print("Batch processing complete!") -``` - -### Custom Inference Config - -Override inference configuration at runtime: - -```python -# Custom configuration for large volumes -custom_config = { - 'patch_size': (64, 64, 64), # Smaller patches for memory constraints - 'patch_stride': (32, 32, 32), # More overlap for smoother results - 'accumulate_device': 'cpu', # Use CPU for accumulation - 'forward_device': 'cuda', # Use GPU for inference - 'forward_batch_windows': 4 # Process 4 patches at once -} - -# Pass to backend initialization (requires custom logic implementation) -``` - -## Performance Optimization - -### GPU Memory Management - -1. **Use FP16 Precision** - - Enable "Use FP16" in the GUI - - Reduces memory usage by ~50% - - Minimal impact on accuracy - -2. **Adjust Patch Size** - - Smaller patches = less GPU memory - - Typical sizes: 64³, 96³, 128³ - - Balance between memory and context - -3. **Enable CPU Accumulation** - - Use "Force CPU Accumulation" for very large volumes - - Accumulate results on CPU to free GPU memory - - Slight performance trade-off - -### Inference Speed - -1. **Batch Processing** - - Process multiple patches simultaneously - - Adjust `forward_batch_windows` in config - -2. **ONNX Runtime** - - Use ONNX backend for optimized inference - - Especially beneficial for deployment - -3. **Optimal Stride** - - Larger stride = faster inference - - Smaller stride = smoother results - - Recommended: stride = patch_size / 2 - -## Troubleshooting - -### Common Issues - -**Problem**: Module doesn't appear in Slicer - -- **Solution**: Check that the extension path is correctly added in Application Settings → Modules - -**Problem**: "ITKIT is not installed" error - -- **Solution**: Install ITKIT in Slicer's Python: - - ```python - import pip - pip.main(['install', 'itkit[advanced]']) - ``` - -**Problem**: CUDA out of memory - -- **Solution**: - - Enable "Force CPU Accumulation" - - Reduce patch size - - Use FP16 precision - -**Problem**: Slow inference - -- **Solution**: - - Enable FP16 - - Use ONNX backend - - Increase patch size if memory allows - - Reduce overlap (larger stride) - -**Problem**: Segmentation results look blocky - -- **Solution**: - - Reduce stride (more overlap) - - Use Gaussian blending (configure in inference_config) - -## Integration with Slicer Workflows - -### Segment Editor Integration - -After inference, use Slicer's Segment Editor to: - -- Refine segmentation results -- Add/remove segments -- Apply smoothing and morphological operations -- Export to various formats - -### Visualization - -- Use 3D view for volume rendering -- Adjust segment colors and opacity -- Create animations and screenshots -- Export to presentation formats - -### Quantification - -Use Slicer's analysis modules: - -- Segment Statistics for volume/surface measurements -- Label Statistics for intensity analysis -- Distance metrics between structures - -## API Reference - -### ITKITInferenceLogic.process() - -Main inference method: - -```python -def process(self, - inputVolume: vtkMRMLScalarVolumeNode, - outputSegmentation: vtkMRMLSegmentationNode | None, - backend: str, - configPath: str | None, - checkpointPath: str, - patchSize: tuple | None, - patchStride: tuple | None, - fp16: bool = False, - forceCpu: bool = False, - progressCallback: Callable | None = None - ) -> vtkMRMLSegmentationNode -``` - -**Parameters:** - -- `inputVolume`: Input 3D volume node -- `outputSegmentation`: Existing segmentation node or None to create new -- `backend`: "MMEngine" or "ONNX" -- `configPath`: Path to config file (MMEngine only) -- `checkpointPath`: Path to model checkpoint/ONNX file -- `patchSize`: Optional sliding window size as tuple (Z, Y, X) -- `patchStride`: Optional stride as tuple (Z, Y, X) -- `fp16`: Use half precision -- `forceCpu`: Force CPU accumulation -- `progressCallback`: Optional callback for progress updates - -**Returns:** - -- Output segmentation node with results - -## Support - -- GitHub: -- Issues: -- Documentation: -- Email: <312065559@qq.com> - -## Contributing - -Contributions to improve the Slicer extension are welcome! Please see [CONTRIBUTING.md](contributing.md) for guidelines. - -## License - -The ITKIT Slicer extension is released under the MIT License, consistent with both ITKIT and 3D Slicer. diff --git a/mkdocs.yml b/mkdocs.yml index 2e34c17..0d8260a 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -15,10 +15,8 @@ nav: - Home: index.md - Getting Started: - Installation: installation.md - - Quick Start: quickstart.md - Dataset Structure: dataset_structure.md - Processing Tools: - - Overview: preprocessing.md - itk_check: itk_check.md - itk_resample: itk_resample.md - itk_orient: itk_orient.md From 173ff161031f92e574ff8b3fc236bad06309d323 Mon Sep 17 00:00:00 2001 From: mgam <312065559@qq.com> Date: Sat, 24 Jan 2026 22:37:58 +0800 Subject: [PATCH 6/7] typo fix --- docs/index.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/index.md b/docs/index.md index 21588f9..a4dc16c 100644 --- a/docs/index.md +++ b/docs/index.md @@ -49,7 +49,7 @@ Welcome to the ITKIT documentation! ITKIT is a user-friendly toolkit built on `S ### Community -- **[Contributing Guide](contributing.md)** - How to contribute to ITKIT +- **[Contributing Guide](contributing.md)** - Development setup - Code style guidelines - Submission process From 9c79d8b0f61da7da16bcab3c9dcb06c70f98665c Mon Sep 17 00:00:00 2001 From: mgam <312065559@qq.com> Date: Sat, 24 Jan 2026 22:43:17 +0800 Subject: [PATCH 7/7] remove two unused utils --- itkit/utils/DevelopUtils.py | 59 --------- itkit/utils/evaluation.py | 254 ------------------------------------ 2 files changed, 313 deletions(-) delete mode 100644 itkit/utils/DevelopUtils.py delete mode 100644 itkit/utils/evaluation.py diff --git a/itkit/utils/DevelopUtils.py b/itkit/utils/DevelopUtils.py deleted file mode 100644 index a70f2bd..0000000 --- a/itkit/utils/DevelopUtils.py +++ /dev/null @@ -1,59 +0,0 @@ -import os -from time import time - -import numpy as np -import torch - - -# function decorator for debug -def measure_time(func): - def wrapper(*args, **kwargs): - start_time = time() - result = func(*args, **kwargs) - end_time = time() - elapsed_time = end_time - start_time - print(f"{func.__qualname__} 执行时间: {elapsed_time:.2f} 秒") - return result - return wrapper - - -# for debug -def InjectVisualize(img, mask): - import matplotlib.pyplot as plt - if isinstance(img, torch.Tensor): - img = img.cpu().numpy() # [B, D, H, W] - if isinstance(mask, torch.Tensor): - mask = mask.cpu().numpy() # [B, D, H, W] - if img.ndim == 3: - img = img[np.newaxis, ...] - mask = mask[np.newaxis, ...] - - fig, ax = plt.subplots(1, 2) - ax[0].imshow(img[0, img.shape[1]//2], cmap='gray') - ax[1].imshow(mask[0, img.shape[1]//2], cmap='rainbow') - os.makedirs('./InjectVisualize', exist_ok=True) - fig.savefig(f'./InjectVisualize/visualize_{time()}.png') - - -# for debug -def InjectVisualize_2D(img1, img2): - import matplotlib.pyplot as plt - if isinstance(img1, torch.Tensor): - img1 = img1.cpu().detach().numpy() # [C, H, W] - if isinstance(img2, torch.Tensor): - img2 = img2.cpu().detach().numpy() # [C, H, W] - if img1.ndim == 3: - img1 = img1.transpose(1, 2, 0) - if img2.ndim == 3: - img2 = img2.transpose(1, 2, 0) - - fig, ax = plt.subplots(1, 2) - ax[0].imshow(img1) - ax[1].imshow(img2) - - # show min, max, mean, std - ax[0].text(0, 0, f"min: {img1.min():.2f}\nmax: {img1.max():.2f}\nmean: {img1.mean():.2f}\nstd: {img1.std():.2f}", fontsize=8, color='blue', transform=ax[0].transAxes) - ax[1].text(0, 0, f"min: {img2.min():.2f}\nmax: {img2.max():.2f}\nmean: {img2.mean():.2f}\nstd: {img2.std():.2f}", fontsize=8, color='blue', transform=ax[1].transAxes) - - os.makedirs('./InjectVisualize', exist_ok=True) - fig.savefig(f'./InjectVisualize/visualize_{time()}.png') diff --git a/itkit/utils/evaluation.py b/itkit/utils/evaluation.py deleted file mode 100644 index c21492a..0000000 --- a/itkit/utils/evaluation.py +++ /dev/null @@ -1,254 +0,0 @@ -import argparse -import multiprocessing as mp -from pathlib import Path - -import numpy as np -import pandas as pd -import SimpleITK as sitk -from tqdm import tqdm - - -def get_meta_info(itk_image:sitk.Image): - return { - 'origin': itk_image.GetOrigin()[::-1], - 'spacing': itk_image.GetSpacing()[::-1], - 'size': itk_image.GetSize()[::-1], - } - - -def get_SeriesUID_from_filename(filename:str): - return Path(filename).stem - - -def calculate_quantitative_metrics(img_itk:sitk.Image, - gt_itk:sitk.Image, - pred_itk:sitk.Image, - num_classes:int): - """ Calculate Dice, IoU, Accuracy, Recall, Precision using scikit-learn - - Args: - img_itk: Original image (not used in metrics but kept for future extensions) - gt_itk: Ground truth segmentation mask - pred_itk: Predicted segmentation mask - num_classes: Number of classes including background - - Returns: - dict: Metrics for each class, keys are metric names with class indices - """ - from sklearn.metrics import ( - accuracy_score, - jaccard_score, - precision_recall_fscore_support, - ) - - # Convert SimpleITK images to numpy arrays - gt_array = sitk.GetArrayFromImage(gt_itk).flatten() - pred_array = sitk.GetArrayFromImage(pred_itk).flatten() - - # Overall accuracy - overall_accuracy = accuracy_score(gt_array, pred_array) - - # Per-class metrics using scikit-learn - # Set zero_division=0 to handle empty classes gracefully - precision, recall, f1score, support = precision_recall_fscore_support( - gt_array, - pred_array, - labels=list(range(num_classes)), - average=None, - zero_division=0 - ) - - # IoU (Jaccard) per class - iou = jaccard_score( - gt_array, - pred_array, - labels=list(range(num_classes)), - average=None, - zero_division=0 - ) - - # Convert to numpy arrays to ensure proper type (avoiding type checker warnings) - precision = np.asarray(precision) - recall = np.asarray(recall) - f1score = np.asarray(f1score) - support = np.asarray(support) - iou = np.asarray(iou) - - # Dice coefficient equals F1 score for binary classification per class - dice = f1score - - # Organize results - results = { - 'overall_accuracy': overall_accuracy, - } - - for class_idx in range(num_classes): - results[f'class_{class_idx}_dice'] = dice[class_idx] - results[f'class_{class_idx}_iou'] = iou[class_idx] - results[f'class_{class_idx}_precision'] = precision[class_idx] - results[f'class_{class_idx}_recall'] = recall[class_idx] - results[f'class_{class_idx}_support'] = support[class_idx] - - return results - - -class Evaluator: - def __init__(self, num_classes:int, workers:int|None=None, class_names:list[str]|None=None): - self.num_classes = num_classes - self.workers = workers or mp.cpu_count() - self.class_names = class_names or [f'Class_{i}' for i in range(num_classes)] - - def executor(self, img_dir:str, gt_dir:str, pred_dir:str, save_path:str): - """ Execute evaluation on multiple samples with multiprocessing - - Args: - img_dir: Directory containing original images (.mha files) - gt_dir: Directory containing ground truth masks (.mha files) - pred_dir: Directory containing prediction masks (.mha files) - save_path: Path to save the evaluation results (xlsx file) - """ - # Get all .mha files from each directory - img_dir_path = Path(img_dir) - gt_dir_path = Path(gt_dir) - pred_dir_path = Path(pred_dir) - - # Find all .mha files and extract their stems (filenames without extension) - img_files = {f.stem: f for f in img_dir_path.glob('*.mha')} - gt_files = {f.stem: f for f in gt_dir_path.glob('*.mha')} - pred_files = {f.stem: f for f in pred_dir_path.glob('*.mha')} - - # Get intersection of all three sets (common SeriesUIDs) - common_series_uids = set(img_files.keys()) & set(gt_files.keys()) & set(pred_files.keys()) - - if not common_series_uids: - raise ValueError("No common .mha files found in all three directories") - - print(f"Found {len(common_series_uids)} common samples across all directories") - print(f" Images directory: {len(img_files)} .mha files") - print(f" Ground truth directory: {len(gt_files)} .mha files") - print(f" Predictions directory: {len(pred_files)} .mha files") - - # Sort for consistent ordering - common_series_uids = sorted(common_series_uids) - - # Build task list with matched file paths - tasks = [ - (str(img_files[uid]), str(gt_files[uid]), str(pred_files[uid])) - for uid in common_series_uids - ] - - results_list = [] - - with mp.Pool(processes=self.workers) as pool: - for result in tqdm( - pool.imap_unordered(self._calculate_one_sample, tasks), - total=len(tasks), - desc="Evaluating samples" - ): - results_list.append(result) - - self._save_results(results_list, save_path) - - return results_list - - def _calculate_one_sample(self, args:tuple[str, str, str]): - """ Calculate metrics for one sample - - Args: - img_path: Path to original image - gt_path: Path to ground truth mask - pred_path: Path to prediction mask - - Returns: - dict: Metrics with SeriesUID and all evaluation metrics - """ - img_path, gt_path, pred_path = args - - img_itk = sitk.ReadImage(img_path) - gt_itk = sitk.ReadImage(gt_path) - pred_itk = sitk.ReadImage(pred_path) - - series_uid = get_SeriesUID_from_filename(pred_path) - quantitative_metrics = calculate_quantitative_metrics(img_itk, gt_itk, pred_itk, self.num_classes) - - # Add SeriesUID and metadata - result = { - 'SeriesUID': series_uid, - **get_meta_info(pred_itk), - **quantitative_metrics - } - - return result - - def _save_results(self, results_list:list[dict], save_path:str): - """ Convert results to xlsx table with organized columns - - Args: - results_list: List of result dictionaries from each sample - save_path: Path to save the xlsx file - """ - if not results_list: - raise FileNotFoundError("No results to save") - - # Convert to DataFrame - df = pd.DataFrame(results_list) - - # Organize columns: SeriesUID first, then metadata, then metrics grouped by class - base_columns = ['SeriesUID', 'origin', 'spacing', 'size', 'overall_accuracy'] - - # Group metrics by class - metric_types = ['dice', 'iou', 'precision', 'recall', 'support'] - class_columns = [] - - for class_idx in range(self.num_classes): - class_name = self.class_names[class_idx] - for metric_type in metric_types: - col_key = f'class_{class_idx}_{metric_type}' - if col_key in df.columns: - # Rename column to include class name for readability - new_col_name = f'{class_name}_{metric_type.capitalize()}' - df.rename(columns={col_key: new_col_name}, inplace=True) - class_columns.append(new_col_name) - - # Reorder columns - ordered_columns = base_columns + class_columns - ordered_columns = [col for col in ordered_columns if col in df.columns] - df = df[ordered_columns] - - # Save and Print - Path(save_path).parent.mkdir(parents=True, exist_ok=True) - df.to_excel(save_path, index=False) - print(f"Results saved to: {save_path}") - print(f"Total samples evaluated: {len(results_list)}") - - -def parse_arg(): - parser = argparse.ArgumentParser(description="Evaluate segmentation results.") - parser.add_argument('-i', '--img-dir', type=str) - parser.add_argument('-g', '--gt-dir', type=str) - parser.add_argument('-p', '--pred-dir', type=str) - parser.add_argument('-s', '--save-path', type=str, default='evaluation_results.xlsx') - parser.add_argument('-n', '--num-classes', type=int) - parser.add_argument('-c', '--class-names', type=str, nargs='*', default=None) - parser.add_argument('-w', '--workers', type=int, default=None) - return parser.parse_args() - - -# Example usage -if __name__ == '__main__': - args = parse_arg() - - # Example: Evaluate a set of predictions - evaluator = Evaluator( - num_classes=args.num_classes, - workers=args.workers, - class_names=args.class_names - ) - - # Execute evaluation by specifying three directories - evaluator.executor( - img_dir=args.img_dir, - gt_dir=args.gt_dir, - pred_dir=args.pred_dir, - save_path=args.save_path - )