diff --git a/docs/contributing.md b/docs/contributing.md index 1eb5c0ad..c1e4e0eb 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 8a02648a..38b452cd 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 54cbe62c..a4dc16c4 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 @@ -51,18 +49,12 @@ 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 - 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 ab2c9201..0bdbf8e2 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 999ede2f..67feb400 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 6599ca4c..00000000 --- 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 1e6c8655..00000000 --- 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 7c9b9bca..805c8ad3 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/examples/configs/0.0.AbdomenCT1K_TorchIO/SegFormer3D.py b/examples/configs/0.0.AbdomenCT1K_TorchIO/SegFormer3D.py index f327b7db..2fa14e09 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.0.AbdomenCT1K_TorchIO/mgam.py b/examples/configs/0.0.AbdomenCT1K_TorchIO/mgam.py index d5990644..ae4fce1c 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/SegFormer3D.py b/examples/configs/0.1.AbdomenCT1K_MONAI/SegFormer3D.py index f327b7db..2fa14e09 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.1.AbdomenCT1K_MONAI/mgam.py b/examples/configs/0.1.AbdomenCT1K_MONAI/mgam.py index 49160ac3..43e5e546 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/SegFormer3D.py b/examples/configs/0.2.AbdomenCT1K_ITKIT/SegFormer3D.py index f327b7db..2fa14e09 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/0.2.AbdomenCT1K_ITKIT/mgam.py b/examples/configs/0.2.AbdomenCT1K_ITKIT/mgam.py index ac67d8aa..e466d640 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/SegFormer3D.py b/examples/configs/1.0.TsdMRI_TorchIO/SegFormer3D.py index f327b7db..2fa14e09 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 67bcb555..1caebcaa 100644 --- a/examples/configs/1.0.TsdMRI_TorchIO/mgam.py +++ b/examples/configs/1.0.TsdMRI_TorchIO/mgam.py @@ -67,9 +67,9 @@ # Dataset data_root = "/mnt/wsl/Fwsldatavhdx/mgam_datasets/TotalSegmentatorMRI/spacing2" num_classes = 57 -wl = 100 # window level -ww = 300 # window width -size = (32,32,32) # [Z, Y, X] +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/SegFormer3D.py b/examples/configs/1.1.TsdMRI_MONAI/SegFormer3D.py index f327b7db..2fa14e09 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 81e3f346..f2acc9bd 100644 --- a/examples/configs/1.1.TsdMRI_MONAI/mgam.py +++ b/examples/configs/1.1.TsdMRI_MONAI/mgam.py @@ -67,9 +67,9 @@ # Dataset data_root = "/mnt/wsl/Fwsldatavhdx/mgam_datasets/TotalSegmentatorMRI/spacing2" num_classes = 57 -wl = 100 # window level -ww = 300 # window width -size = (32,32,32) # [Z, Y, X] +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/SegFormer3D.py b/examples/configs/1.2.TsdMRI_ITKIT/SegFormer3D.py index f327b7db..2fa14e09 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 d595ef70..e1adedad 100644 --- a/examples/configs/1.2.TsdMRI_ITKIT/mgam.py +++ b/examples/configs/1.2.TsdMRI_ITKIT/mgam.py @@ -66,9 +66,9 @@ # Dataset data_root = "/mnt/wsl/Fwsldatavhdx/mgam_datasets/TotalSegmentatorMRI/spacing2" num_classes = 57 -wl = 100 # window level -ww = 300 # window width -size = (32,32,32) # [Z, Y, X] +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/itkit/dataset/base.py b/itkit/dataset/base.py index 29d43dcb..b9154b48 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 diff --git a/itkit/utils/DevelopUtils.py b/itkit/utils/DevelopUtils.py deleted file mode 100644 index a70f2bd7..00000000 --- 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 c21492ac..00000000 --- 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 - ) diff --git a/mkdocs.yml b/mkdocs.yml index 2e34c17a..0d8260aa 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 diff --git a/notebooks/filter_itk_patched_only_background.ipynb b/notebooks/filter_itk_patched_only_background.ipynb deleted file mode 100644 index fd182770..00000000 --- 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 -} diff --git a/pyproject.toml b/pyproject.toml index 995d4eae..b4925241 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"