kneeseg 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- kneeseg-0.1.0/MANIFEST.in +4 -0
- kneeseg-0.1.0/PKG-INFO +133 -0
- kneeseg-0.1.0/README.md +116 -0
- kneeseg-0.1.0/kneeseg/__init__.py +0 -0
- kneeseg-0.1.0/kneeseg/bone_rf.py +142 -0
- kneeseg-0.1.0/kneeseg/configs/config_schema.json +144 -0
- kneeseg-0.1.0/kneeseg/configs/example_train_config.json +6 -0
- kneeseg-0.1.0/kneeseg/configs/ski10_full_inference_config.json +21 -0
- kneeseg-0.1.0/kneeseg/configs/ski10_full_train_config.json +24 -0
- kneeseg-0.1.0/kneeseg/data/ski10_full_split.json +106 -0
- kneeseg-0.1.0/kneeseg/features.py +214 -0
- kneeseg-0.1.0/kneeseg/io.py +108 -0
- kneeseg-0.1.0/kneeseg/pipeline/__init__.py +0 -0
- kneeseg-0.1.0/kneeseg/pipeline/inference.py +140 -0
- kneeseg-0.1.0/kneeseg/pipeline/run.py +73 -0
- kneeseg-0.1.0/kneeseg/pipeline/train.py +87 -0
- kneeseg-0.1.0/kneeseg/random_walker.py +67 -0
- kneeseg-0.1.0/kneeseg/registration.py +28 -0
- kneeseg-0.1.0/kneeseg/rf_seg.py +108 -0
- kneeseg-0.1.0/kneeseg/tests/__init__.py +0 -0
- kneeseg-0.1.0/kneeseg/tests/test_core.py +63 -0
- kneeseg-0.1.0/kneeseg/tests/test_features.py +20 -0
- kneeseg-0.1.0/kneeseg/tests/test_io.py +31 -0
- kneeseg-0.1.0/kneeseg.egg-info/PKG-INFO +133 -0
- kneeseg-0.1.0/kneeseg.egg-info/SOURCES.txt +29 -0
- kneeseg-0.1.0/kneeseg.egg-info/dependency_links.txt +1 -0
- kneeseg-0.1.0/kneeseg.egg-info/entry_points.txt +2 -0
- kneeseg-0.1.0/kneeseg.egg-info/requires.txt +6 -0
- kneeseg-0.1.0/kneeseg.egg-info/top_level.txt +1 -0
- kneeseg-0.1.0/pyproject.toml +33 -0
- kneeseg-0.1.0/setup.cfg +4 -0
kneeseg-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: kneeseg
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: A Python reimplementation of Semantic Context Forests for Knee Cartilage Segmentation
|
|
5
|
+
Author-email: Quan Wang <quanw@google.com>
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Classifier: Programming Language :: Python :: 3
|
|
8
|
+
Classifier: Operating System :: OS Independent
|
|
9
|
+
Requires-Python: >=3.8
|
|
10
|
+
Description-Content-Type: text/markdown
|
|
11
|
+
Requires-Dist: numpy
|
|
12
|
+
Requires-Dist: scipy
|
|
13
|
+
Requires-Dist: scikit-learn
|
|
14
|
+
Requires-Dist: joblib
|
|
15
|
+
Requires-Dist: tqdm
|
|
16
|
+
Requires-Dist: SimpleITK
|
|
17
|
+
|
|
18
|
+
# kneeseg: Knee Bone & Cartilage Segmentation in 3D MRI
|
|
19
|
+
|
|
20
|
+
[](https://github.com/wq2012/kneeseg/actions/workflows/python-package.yml)
|
|
21
|
+
|
|
22
|
+
**kneeseg** is a Python reimplementation of the paper "Semantic Context Forests for Learning-Based Knee Cartilage Segmentation in 3D MR Images". [[paper](resources/knee_MCV_2013_paper.pdf)] [[slides](resources/knee_MCV_2013_slides.pdf)]
|
|
23
|
+
|
|
24
|
+
| | Original MICCAI Workshop Paper | This Implementation |
|
|
25
|
+
|-----------------|----------------|---------------------|
|
|
26
|
+
| **Bone Segmentation** | Active Shape Model (Siemens proprietary) | Dense Random Forest (Auto-Context) |
|
|
27
|
+
| **Cartilage Segmentation** | [Semantic Context Forest (C++)](https://github.com/wq2012/DecisionForest) | Semantic Context Forest (Python) |
|
|
28
|
+
| **Dataset** | Osteoarthritis Initiative (OAI) | [SKI10](https://ski10.grand-challenge.org/) |
|
|
29
|
+
|
|
30
|
+
## Table of Contents
|
|
31
|
+
1. [Installation](#installation)
|
|
32
|
+
2. [Usage](#usage)
|
|
33
|
+
3. [Experiments Folder](#experiments-folder)
|
|
34
|
+
4. [Experiment Results (SKI10)](#experiment-results-ski10)
|
|
35
|
+
5. [Algorithm Details](#algorithm-details)
|
|
36
|
+
6. [Citation](#citation)
|
|
37
|
+
|
|
38
|
+
## Installation
|
|
39
|
+
|
|
40
|
+
You can install the package via pip:
|
|
41
|
+
|
|
42
|
+
```bash
|
|
43
|
+
pip install kneeseg
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
## Usage
|
|
47
|
+
|
|
48
|
+
### As a Library
|
|
49
|
+
```python
|
|
50
|
+
from kneeseg.io import load_volume
|
|
51
|
+
from kneeseg.bone_rf import BoneClassifier
|
|
52
|
+
# ...
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
### Running the Pipeline
|
|
56
|
+
The package includes a unified pipeline script `kneeseg-pipeline` (or via python module):
|
|
57
|
+
|
|
58
|
+
```bash
|
|
59
|
+
# Run using the installed command
|
|
60
|
+
kneeseg-pipeline --data-dir /path/to/SKI10/data
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
> For advanced usage and reproduction scripts (e.g., training models from scratch), please refer to the [Experiments Documentation](experiments/README.md).
|
|
64
|
+
|
|
65
|
+
## Experiments Folder
|
|
66
|
+
The `experiments/` directory contains reproduceable scripts and will store the output models (`models/`) and predictions (`predictions/`) if you run the scripts provided there. See [experiments/README.md](experiments/README.md) for details.
|
|
67
|
+
|
|
68
|
+
## Experiment Results (SKI10)
|
|
69
|
+
Since the [SKI10 dataset](https://ski10.grand-challenge.org/) doesn not provide the ground truth labels for its default testing set, we evaluated the pipeline on a **20% hold-out set** (20 cases) from the SKI10 training data (Total 80 cases: 60 Train, 20 Eval).
|
|
70
|
+
|
|
71
|
+
### Metrics
|
|
72
|
+
| Structure | Dice Similarity Coefficient (DSC) |
|
|
73
|
+
|-----------|-----------------------------------|
|
|
74
|
+
| **Femur** | 0.9046 ± 0.0361 |
|
|
75
|
+
| **Tibia** | 0.9292 ± 0.0260 |
|
|
76
|
+
| **Femoral Cartilage** | 0.5944 ± 0.0654 |
|
|
77
|
+
| **Tibial Cartilage** | 0.5805 ± 0.0533 |
|
|
78
|
+
|
|
79
|
+
### Evaluation Set
|
|
80
|
+
The following 20 cases were held out for evaluation:
|
|
81
|
+
`image-004`, `image-005`, `image-012`, `image-014`, `image-015`, `image-018`, `image-028`, `image-029`, `image-030`, `image-032`, `image-036`, `image-055`, `image-065`, `image-070`, `image-076`, `image-082`, `image-087`, `image-089`, `image-095`, `image-098`.
|
|
82
|
+
|
|
83
|
+
## Algorithm Details
|
|
84
|
+
|
|
85
|
+
### Bone Segmentation (Dense Auto-Context RF)
|
|
86
|
+
1. **Pass 1**: Dense Random Forest voxel classification.
|
|
87
|
+
- **Features**: Normalized Intensity, Gaussian Smoothed Intensity ($\sigma=2.0, 4.0$), Spatial Coordinates, RSID (20 offsets).
|
|
88
|
+
- **Target**: 3-class classification (Background, Femur, Tibia).
|
|
89
|
+
|
|
90
|
+
2. **Pass 2 (Refinement)**: Auto-Context Random Forest.
|
|
91
|
+
- **Features**: All Pass 1 features + **Probabilities from Pass 1**.
|
|
92
|
+
- **Performance**: Achieves >0.90 DSC on Bones.
|
|
93
|
+
|
|
94
|
+
### Cartilage Segmentation (Semantic Context Forest)
|
|
95
|
+
1. **Feature Extraction**:
|
|
96
|
+
- **Semantic Context**: Signed Distance Transforms (SDT) computed from Pass 2 bone masks.
|
|
97
|
+
- **Texture**: RSID (30 offsets).
|
|
98
|
+
- **Local**: Intensity, Gaussian ($\sigma=1.0$), Gradient.
|
|
99
|
+
- **Arithmetic**: DT Sum/Diff.
|
|
100
|
+
2. **Classification**: Dense Random Forest (100 trees).
|
|
101
|
+
- **Performance**: Achieves ~0.60 DSC.
|
|
102
|
+
|
|
103
|
+
## Citation
|
|
104
|
+
|
|
105
|
+
**Plain Text:**
|
|
106
|
+
|
|
107
|
+
> Quan Wang, Dijia Wu, Le Lu, Meizhu Liu, Kim L. Boyer, and Shaohua Kevin Zhou.
|
|
108
|
+
> "Semantic Context Forests for Learning-Based Knee Cartilage Segmentation in 3D MR Images."
|
|
109
|
+
> MICCAI 2013: Workshop on Medical Computer Vision.
|
|
110
|
+
|
|
111
|
+
> Quan Wang.
|
|
112
|
+
> Exploiting Geometric and Spatial Constraints for Vision and Lighting Applications.
|
|
113
|
+
> Ph.D. dissertation, Rensselaer Polytechnic Institute, 2014.
|
|
114
|
+
|
|
115
|
+
**BibTeX:**
|
|
116
|
+
|
|
117
|
+
```bibtex
|
|
118
|
+
@inproceedings{wang2013semantic,
|
|
119
|
+
title={Semantic context forests for learning-based knee cartilage segmentation in 3D MR images},
|
|
120
|
+
author={Wang, Quan and Wu, Dijia and Lu, Le and Liu, Meizhu and Boyer, Kim L and Zhou, Shaohua Kevin},
|
|
121
|
+
booktitle={International MICCAI Workshop on Medical Computer Vision},
|
|
122
|
+
pages={105--115},
|
|
123
|
+
year={2013},
|
|
124
|
+
organization={Springer}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
@phdthesis{wang2014exploiting,
|
|
128
|
+
title={Exploiting Geometric and Spatial Constraints for Vision and Lighting Applications},
|
|
129
|
+
author={Quan Wang},
|
|
130
|
+
year={2014},
|
|
131
|
+
school={Rensselaer Polytechnic Institute},
|
|
132
|
+
}
|
|
133
|
+
```
|
kneeseg-0.1.0/README.md
ADDED
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
# kneeseg: Knee Bone & Cartilage Segmentation in 3D MRI
|
|
2
|
+
|
|
3
|
+
[](https://github.com/wq2012/kneeseg/actions/workflows/python-package.yml)
|
|
4
|
+
|
|
5
|
+
**kneeseg** is a Python reimplementation of the paper "Semantic Context Forests for Learning-Based Knee Cartilage Segmentation in 3D MR Images". [[paper](resources/knee_MCV_2013_paper.pdf)] [[slides](resources/knee_MCV_2013_slides.pdf)]
|
|
6
|
+
|
|
7
|
+
| | Original MICCAI Workshop Paper | This Implementation |
|
|
8
|
+
|-----------------|----------------|---------------------|
|
|
9
|
+
| **Bone Segmentation** | Active Shape Model (Siemens proprietary) | Dense Random Forest (Auto-Context) |
|
|
10
|
+
| **Cartilage Segmentation** | [Semantic Context Forest (C++)](https://github.com/wq2012/DecisionForest) | Semantic Context Forest (Python) |
|
|
11
|
+
| **Dataset** | Osteoarthritis Initiative (OAI) | [SKI10](https://ski10.grand-challenge.org/) |
|
|
12
|
+
|
|
13
|
+
## Table of Contents
|
|
14
|
+
1. [Installation](#installation)
|
|
15
|
+
2. [Usage](#usage)
|
|
16
|
+
3. [Experiments Folder](#experiments-folder)
|
|
17
|
+
4. [Experiment Results (SKI10)](#experiment-results-ski10)
|
|
18
|
+
5. [Algorithm Details](#algorithm-details)
|
|
19
|
+
6. [Citation](#citation)
|
|
20
|
+
|
|
21
|
+
## Installation
|
|
22
|
+
|
|
23
|
+
You can install the package via pip:
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
pip install kneeseg
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
## Usage
|
|
30
|
+
|
|
31
|
+
### As a Library
|
|
32
|
+
```python
|
|
33
|
+
from kneeseg.io import load_volume
|
|
34
|
+
from kneeseg.bone_rf import BoneClassifier
|
|
35
|
+
# ...
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
### Running the Pipeline
|
|
39
|
+
The package includes a unified pipeline script `kneeseg-pipeline` (or via python module):
|
|
40
|
+
|
|
41
|
+
```bash
|
|
42
|
+
# Run using the installed command
|
|
43
|
+
kneeseg-pipeline --data-dir /path/to/SKI10/data
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
> For advanced usage and reproduction scripts (e.g., training models from scratch), please refer to the [Experiments Documentation](experiments/README.md).
|
|
47
|
+
|
|
48
|
+
## Experiments Folder
|
|
49
|
+
The `experiments/` directory contains reproduceable scripts and will store the output models (`models/`) and predictions (`predictions/`) if you run the scripts provided there. See [experiments/README.md](experiments/README.md) for details.
|
|
50
|
+
|
|
51
|
+
## Experiment Results (SKI10)
|
|
52
|
+
Since the [SKI10 dataset](https://ski10.grand-challenge.org/) doesn not provide the ground truth labels for its default testing set, we evaluated the pipeline on a **20% hold-out set** (20 cases) from the SKI10 training data (Total 80 cases: 60 Train, 20 Eval).
|
|
53
|
+
|
|
54
|
+
### Metrics
|
|
55
|
+
| Structure | Dice Similarity Coefficient (DSC) |
|
|
56
|
+
|-----------|-----------------------------------|
|
|
57
|
+
| **Femur** | 0.9046 ± 0.0361 |
|
|
58
|
+
| **Tibia** | 0.9292 ± 0.0260 |
|
|
59
|
+
| **Femoral Cartilage** | 0.5944 ± 0.0654 |
|
|
60
|
+
| **Tibial Cartilage** | 0.5805 ± 0.0533 |
|
|
61
|
+
|
|
62
|
+
### Evaluation Set
|
|
63
|
+
The following 20 cases were held out for evaluation:
|
|
64
|
+
`image-004`, `image-005`, `image-012`, `image-014`, `image-015`, `image-018`, `image-028`, `image-029`, `image-030`, `image-032`, `image-036`, `image-055`, `image-065`, `image-070`, `image-076`, `image-082`, `image-087`, `image-089`, `image-095`, `image-098`.
|
|
65
|
+
|
|
66
|
+
## Algorithm Details
|
|
67
|
+
|
|
68
|
+
### Bone Segmentation (Dense Auto-Context RF)
|
|
69
|
+
1. **Pass 1**: Dense Random Forest voxel classification.
|
|
70
|
+
- **Features**: Normalized Intensity, Gaussian Smoothed Intensity ($\sigma=2.0, 4.0$), Spatial Coordinates, RSID (20 offsets).
|
|
71
|
+
- **Target**: 3-class classification (Background, Femur, Tibia).
|
|
72
|
+
|
|
73
|
+
2. **Pass 2 (Refinement)**: Auto-Context Random Forest.
|
|
74
|
+
- **Features**: All Pass 1 features + **Probabilities from Pass 1**.
|
|
75
|
+
- **Performance**: Achieves >0.90 DSC on Bones.
|
|
76
|
+
|
|
77
|
+
### Cartilage Segmentation (Semantic Context Forest)
|
|
78
|
+
1. **Feature Extraction**:
|
|
79
|
+
- **Semantic Context**: Signed Distance Transforms (SDT) computed from Pass 2 bone masks.
|
|
80
|
+
- **Texture**: RSID (30 offsets).
|
|
81
|
+
- **Local**: Intensity, Gaussian ($\sigma=1.0$), Gradient.
|
|
82
|
+
- **Arithmetic**: DT Sum/Diff.
|
|
83
|
+
2. **Classification**: Dense Random Forest (100 trees).
|
|
84
|
+
- **Performance**: Achieves ~0.60 DSC.
|
|
85
|
+
|
|
86
|
+
## Citation
|
|
87
|
+
|
|
88
|
+
**Plain Text:**
|
|
89
|
+
|
|
90
|
+
> Quan Wang, Dijia Wu, Le Lu, Meizhu Liu, Kim L. Boyer, and Shaohua Kevin Zhou.
|
|
91
|
+
> "Semantic Context Forests for Learning-Based Knee Cartilage Segmentation in 3D MR Images."
|
|
92
|
+
> MICCAI 2013: Workshop on Medical Computer Vision.
|
|
93
|
+
|
|
94
|
+
> Quan Wang.
|
|
95
|
+
> Exploiting Geometric and Spatial Constraints for Vision and Lighting Applications.
|
|
96
|
+
> Ph.D. dissertation, Rensselaer Polytechnic Institute, 2014.
|
|
97
|
+
|
|
98
|
+
**BibTeX:**
|
|
99
|
+
|
|
100
|
+
```bibtex
|
|
101
|
+
@inproceedings{wang2013semantic,
|
|
102
|
+
title={Semantic context forests for learning-based knee cartilage segmentation in 3D MR images},
|
|
103
|
+
author={Wang, Quan and Wu, Dijia and Lu, Le and Liu, Meizhu and Boyer, Kim L and Zhou, Shaohua Kevin},
|
|
104
|
+
booktitle={International MICCAI Workshop on Medical Computer Vision},
|
|
105
|
+
pages={105--115},
|
|
106
|
+
year={2013},
|
|
107
|
+
organization={Springer}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
@phdthesis{wang2014exploiting,
|
|
111
|
+
title={Exploiting Geometric and Spatial Constraints for Vision and Lighting Applications},
|
|
112
|
+
author={Quan Wang},
|
|
113
|
+
year={2014},
|
|
114
|
+
school={Rensselaer Polytechnic Institute},
|
|
115
|
+
}
|
|
116
|
+
```
|
|
File without changes
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
|
|
2
|
+
from sklearn.ensemble import RandomForestClassifier
|
|
3
|
+
import numpy as np
|
|
4
|
+
import joblib
|
|
5
|
+
from .features import extract_features, compute_rsid_features
|
|
6
|
+
from scipy.ndimage import gaussian_filter
|
|
7
|
+
|
|
8
|
+
class BoneClassifier:
|
|
9
|
+
def __init__(self, n_estimators=100, max_depth=25, n_jobs=-1):
|
|
10
|
+
self.clf = RandomForestClassifier(
|
|
11
|
+
n_estimators=n_estimators,
|
|
12
|
+
max_depth=max_depth,
|
|
13
|
+
n_jobs=n_jobs,
|
|
14
|
+
random_state=42,
|
|
15
|
+
class_weight='balanced'
|
|
16
|
+
)
|
|
17
|
+
|
|
18
|
+
def extract_bone_features(self, image, prob_map=None, spacing=None):
|
|
19
|
+
"""
|
|
20
|
+
Extract features suitable for Bone Segmentation.
|
|
21
|
+
Focuses on larger context and spatial coordinates.
|
|
22
|
+
"""
|
|
23
|
+
features = []
|
|
24
|
+
|
|
25
|
+
# Normalize
|
|
26
|
+
img_mean = image.mean()
|
|
27
|
+
img_std = image.std()
|
|
28
|
+
img_norm = (image.astype(np.float32) - img_mean) / (img_std + 1e-6)
|
|
29
|
+
|
|
30
|
+
# 1. Intensity & Smooth (Multi-scale)
|
|
31
|
+
features.append(img_norm.flatten())
|
|
32
|
+
features.append(gaussian_filter(img_norm, sigma=2.0).flatten())
|
|
33
|
+
features.append(gaussian_filter(img_norm, sigma=4.0).flatten())
|
|
34
|
+
|
|
35
|
+
# 2. Spatial Coordinates (Normalized 0-1)
|
|
36
|
+
z, y, x = np.mgrid[0:image.shape[0], 0:image.shape[1], 0:image.shape[2]]
|
|
37
|
+
features.append(z.flatten() / image.shape[0])
|
|
38
|
+
features.append(y.flatten() / image.shape[1])
|
|
39
|
+
features.append(x.flatten() / image.shape[2])
|
|
40
|
+
|
|
41
|
+
# 3. RSID (Texture) - Sparse but helpful
|
|
42
|
+
# Keep shift small-ish but larger than cartilage
|
|
43
|
+
# Downsample image for RSID to save memory? No, standard RSID.
|
|
44
|
+
rsid = compute_rsid_features(img_norm, num_shifts=20, max_shift=20, seed=42)
|
|
45
|
+
for i in range(rsid.shape[-1]):
|
|
46
|
+
features.append(rsid[..., i].flatten())
|
|
47
|
+
|
|
48
|
+
# 4. Auto-Context Probabilities
|
|
49
|
+
if prob_map is not None:
|
|
50
|
+
# prob_map is (Z, Y, X, C)
|
|
51
|
+
for c in range(prob_map.shape[-1]):
|
|
52
|
+
p_ch = prob_map[..., c]
|
|
53
|
+
features.append(p_ch.flatten())
|
|
54
|
+
features.append(gaussian_filter(p_ch, sigma=2.0).flatten())
|
|
55
|
+
|
|
56
|
+
return np.stack(features, axis=1)
|
|
57
|
+
|
|
58
|
+
def train(self, images, labels, prob_maps=None, subsample=100000):
|
|
59
|
+
"""
|
|
60
|
+
images: list of 3D arrays
|
|
61
|
+
labels: list of 3D arrays (0=bg, 1=Femur, 2=FemCart, 3=Tibia, 4=TibCart)
|
|
62
|
+
prob_maps: list of 3D probability maps (optional)
|
|
63
|
+
"""
|
|
64
|
+
X_all = []
|
|
65
|
+
y_all = []
|
|
66
|
+
|
|
67
|
+
print(f" Extracting features for Bone RF (ProbMap={prob_maps is not None})...")
|
|
68
|
+
for i, (img, lbl) in enumerate(zip(images, labels)):
|
|
69
|
+
# Map Labels
|
|
70
|
+
# Femur(1) + FemCart(2) -> Femur (1)
|
|
71
|
+
# Tibia(3) + TibCart(4) -> Tibia (2)
|
|
72
|
+
# Background -> 0
|
|
73
|
+
|
|
74
|
+
y_mapped = np.zeros_like(lbl, dtype=np.uint8)
|
|
75
|
+
y_mapped[lbl == 1] = 1 # Femur
|
|
76
|
+
y_mapped[lbl == 2] = 1 # FemCart -> Femur (Segment the whole joint head)
|
|
77
|
+
y_mapped[lbl == 3] = 2 # Tibia
|
|
78
|
+
y_mapped[lbl == 4] = 2 # TibCart -> Tibia
|
|
79
|
+
|
|
80
|
+
# Subsample
|
|
81
|
+
# We need balanced sampling.
|
|
82
|
+
# 1. All Bone Voxels (or large subset)
|
|
83
|
+
# 2. Subset of Background (especially near bones)
|
|
84
|
+
|
|
85
|
+
mask_bone = (y_mapped > 0)
|
|
86
|
+
target_indices = np.argwhere(mask_bone)
|
|
87
|
+
|
|
88
|
+
# Dilate bone mask to find "Near Background"
|
|
89
|
+
from scipy.ndimage import binary_dilation
|
|
90
|
+
mask_near = binary_dilation(mask_bone, iterations=10) & (~mask_bone)
|
|
91
|
+
near_indices = np.argwhere(mask_near)
|
|
92
|
+
|
|
93
|
+
# Far background
|
|
94
|
+
far_indices = np.argwhere((~mask_bone) & (~mask_near))
|
|
95
|
+
|
|
96
|
+
# Select samples
|
|
97
|
+
n_bone = len(target_indices)
|
|
98
|
+
n_near = min(len(near_indices), n_bone) # 1:1 Bone:Near
|
|
99
|
+
n_far = min(len(far_indices), n_bone // 4) # fewer far samples
|
|
100
|
+
|
|
101
|
+
idx_bone = np.random.choice(len(target_indices), min(n_bone, subsample//2), replace=False)
|
|
102
|
+
idx_near = np.random.choice(len(near_indices), min(n_near, subsample//4), replace=False)
|
|
103
|
+
idx_far = np.random.choice(len(far_indices), min(n_far, subsample//8), replace=False)
|
|
104
|
+
|
|
105
|
+
coords = np.vstack([
|
|
106
|
+
target_indices[idx_bone],
|
|
107
|
+
near_indices[idx_near],
|
|
108
|
+
far_indices[idx_far]
|
|
109
|
+
])
|
|
110
|
+
|
|
111
|
+
pm = prob_maps[i] if prob_maps else None
|
|
112
|
+
feats_flat = self.extract_bone_features(img, prob_map=pm) # (N_all, F)
|
|
113
|
+
|
|
114
|
+
# Convert 3D coords to 1D indices
|
|
115
|
+
flat_indices = np.ravel_multi_index(coords.T, img.shape)
|
|
116
|
+
|
|
117
|
+
X_case = feats_flat[flat_indices]
|
|
118
|
+
y_case = y_mapped.flatten()[flat_indices]
|
|
119
|
+
|
|
120
|
+
X_all.append(X_case)
|
|
121
|
+
y_all.append(y_case)
|
|
122
|
+
|
|
123
|
+
print(" Fitting Bone Random Forest...")
|
|
124
|
+
self.clf.fit(np.vstack(X_all), np.concatenate(y_all))
|
|
125
|
+
|
|
126
|
+
def predict(self, image, prob_map=None):
|
|
127
|
+
feats_flat = self.extract_bone_features(image, prob_map)
|
|
128
|
+
|
|
129
|
+
# Predict in chunks to be safe? Or full.
|
|
130
|
+
# 100GB RAM. Image ~10M voxels. Features ~30 floats -> 300MB * 4 = 1.2GB.
|
|
131
|
+
# Easy.
|
|
132
|
+
|
|
133
|
+
y_pred = self.clf.predict(feats_flat)
|
|
134
|
+
y_prob = self.clf.predict_proba(feats_flat)
|
|
135
|
+
|
|
136
|
+
return y_pred.reshape(image.shape), y_prob.reshape(image.shape + (-1,))
|
|
137
|
+
|
|
138
|
+
def save(self, path):
|
|
139
|
+
joblib.dump(self.clf, path)
|
|
140
|
+
|
|
141
|
+
def load(self, path):
|
|
142
|
+
self.clf = joblib.load(path)
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "http://json-schema.org/draft-07/schema#",
|
|
3
|
+
"title": "Knee Segmentation Configuration",
|
|
4
|
+
"type": "object",
|
|
5
|
+
"properties": {
|
|
6
|
+
"data_config": {
|
|
7
|
+
"type": "object",
|
|
8
|
+
"description": "Configuration for data directories and file lists",
|
|
9
|
+
"properties": {
|
|
10
|
+
"image_directory": {
|
|
11
|
+
"type": "string",
|
|
12
|
+
"description": "Path to the directory containing input image volumes (e.g., .mhd or .hdr files)."
|
|
13
|
+
},
|
|
14
|
+
"label_directory": {
|
|
15
|
+
"type": "string",
|
|
16
|
+
"description": "Path to the directory containing ground truth label volumes. Required for training and optional evaluation."
|
|
17
|
+
},
|
|
18
|
+
"split_file": {
|
|
19
|
+
"type": "string",
|
|
20
|
+
"description": "Optional path to a JSON file defining 'train' and 'eval' file lists."
|
|
21
|
+
},
|
|
22
|
+
"select_files": {
|
|
23
|
+
"type": "array",
|
|
24
|
+
"items": {
|
|
25
|
+
"type": "string"
|
|
26
|
+
},
|
|
27
|
+
"description": "Explicit list of filenames to process. If provided, overrides all other filtering."
|
|
28
|
+
}
|
|
29
|
+
},
|
|
30
|
+
"required": [
|
|
31
|
+
"image_directory"
|
|
32
|
+
],
|
|
33
|
+
"additionalProperties": false
|
|
34
|
+
},
|
|
35
|
+
"training_config": {
|
|
36
|
+
"type": "object",
|
|
37
|
+
"description": "Parameters for model training",
|
|
38
|
+
"properties": {
|
|
39
|
+
"augmentation": {
|
|
40
|
+
"type": "boolean",
|
|
41
|
+
"default": false,
|
|
42
|
+
"description": "Whether to apply random data augmentation (rotation, noise) during training."
|
|
43
|
+
},
|
|
44
|
+
"bone_parameters": {
|
|
45
|
+
"type": "object",
|
|
46
|
+
"properties": {
|
|
47
|
+
"pca_components": {
|
|
48
|
+
"type": "integer",
|
|
49
|
+
"minimum": 1,
|
|
50
|
+
"description": "Number of principal components for the Active Shape Model."
|
|
51
|
+
},
|
|
52
|
+
"subsample_points": {
|
|
53
|
+
"type": "integer",
|
|
54
|
+
"default": 1000,
|
|
55
|
+
"description": "Number of surface points to sample per case for PCA training."
|
|
56
|
+
}
|
|
57
|
+
},
|
|
58
|
+
"additionalProperties": false
|
|
59
|
+
},
|
|
60
|
+
"cartilage_parameters": {
|
|
61
|
+
"type": "object",
|
|
62
|
+
"properties": {
|
|
63
|
+
"n_estimators": {
|
|
64
|
+
"type": "integer",
|
|
65
|
+
"default": 100,
|
|
66
|
+
"description": "Number of trees in the Random Forest classifier."
|
|
67
|
+
},
|
|
68
|
+
"max_depth": {
|
|
69
|
+
"type": "integer",
|
|
70
|
+
"default": 15,
|
|
71
|
+
"description": "Maximum depth of the Random Forest trees."
|
|
72
|
+
},
|
|
73
|
+
"subsample_voxels_per_case": {
|
|
74
|
+
"type": "integer",
|
|
75
|
+
"default": 50000,
|
|
76
|
+
"description": "Max number of voxels to sample per training case to balance speed and memory."
|
|
77
|
+
},
|
|
78
|
+
"training_proximity_mm": {
|
|
79
|
+
"type": "number",
|
|
80
|
+
"default": 10.0,
|
|
81
|
+
"description": "Proximity (in mm) to bone surfaces for selecting cartilage training samples."
|
|
82
|
+
}
|
|
83
|
+
},
|
|
84
|
+
"additionalProperties": false
|
|
85
|
+
}
|
|
86
|
+
},
|
|
87
|
+
"additionalProperties": false
|
|
88
|
+
},
|
|
89
|
+
"inference_config": {
|
|
90
|
+
"type": "object",
|
|
91
|
+
"description": "Parameters for prediction and post-processing",
|
|
92
|
+
"properties": {
|
|
93
|
+
"use_random_walker": {
|
|
94
|
+
"type": "boolean",
|
|
95
|
+
"default": false,
|
|
96
|
+
"description": "Whether to apply Random Walker refinement to the initial classifier output."
|
|
97
|
+
},
|
|
98
|
+
"random_walker_beta": {
|
|
99
|
+
"type": "number",
|
|
100
|
+
"default": 130.0,
|
|
101
|
+
"description": "Beta parameter for the Random Walker (controls edge sensitivity)."
|
|
102
|
+
},
|
|
103
|
+
"inference_proximity_mm": {
|
|
104
|
+
"type": "number",
|
|
105
|
+
"default": 15.0,
|
|
106
|
+
"description": "Proximity (in mm) to detected bone surfaces for cartilage classification."
|
|
107
|
+
},
|
|
108
|
+
"perform_evaluation": {
|
|
109
|
+
"type": "boolean",
|
|
110
|
+
"default": false,
|
|
111
|
+
"description": "If true, the inference script will attempt to calculate Dice scores against ground truth."
|
|
112
|
+
}
|
|
113
|
+
},
|
|
114
|
+
"additionalProperties": false
|
|
115
|
+
},
|
|
116
|
+
"output_config": {
|
|
117
|
+
"type": "object",
|
|
118
|
+
"description": "Configuration for saving results",
|
|
119
|
+
"properties": {
|
|
120
|
+
"model_directory": {
|
|
121
|
+
"type": "string",
|
|
122
|
+
"description": "Directory where trained models (.joblib) will be saved or loaded from."
|
|
123
|
+
},
|
|
124
|
+
"prediction_directory": {
|
|
125
|
+
"type": "string",
|
|
126
|
+
"description": "Directory where predicted segmentation volumes will be saved."
|
|
127
|
+
},
|
|
128
|
+
"evaluation_report_path": {
|
|
129
|
+
"type": "string",
|
|
130
|
+
"description": "Path to save the final quantitative evaluation report (JSON)."
|
|
131
|
+
}
|
|
132
|
+
},
|
|
133
|
+
"required": [
|
|
134
|
+
"model_directory"
|
|
135
|
+
],
|
|
136
|
+
"additionalProperties": false
|
|
137
|
+
}
|
|
138
|
+
},
|
|
139
|
+
"required": [
|
|
140
|
+
"data_config",
|
|
141
|
+
"output_config"
|
|
142
|
+
],
|
|
143
|
+
"additionalProperties": false
|
|
144
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
|
|
2
|
+
{
|
|
3
|
+
"data_config": {
|
|
4
|
+
"image_directory": "/usr/local/google/home/quanw/Code/siemens_2012/Data/SKI10/TrainingData",
|
|
5
|
+
"label_directory": "/usr/local/google/home/quanw/Code/siemens_2012/Data/SKI10/TrainingData_labels",
|
|
6
|
+
"split_file": "/usr/local/google/home/quanw/Code/siemens_2012/experiments/ski10_full_split.json"
|
|
7
|
+
},
|
|
8
|
+
"inference_config": {
|
|
9
|
+
"inference_proximity_mm": 10.0,
|
|
10
|
+
"use_random_walker": true,
|
|
11
|
+
"random_walker_beta": 130.0,
|
|
12
|
+
"active_search_iters": 20,
|
|
13
|
+
"active_search_range": 25
|
|
14
|
+
},
|
|
15
|
+
"model_config": {
|
|
16
|
+
"model_directory": "/usr/local/google/home/quanw/Code/siemens_2012/experiments/ski10_full_models_repro"
|
|
17
|
+
},
|
|
18
|
+
"output_config": {
|
|
19
|
+
"prediction_directory": "/usr/local/google/home/quanw/Code/siemens_2012/experiments/ski10_improved_predictions"
|
|
20
|
+
}
|
|
21
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
|
|
2
|
+
{
|
|
3
|
+
"data_config": {
|
|
4
|
+
"image_directory": "/usr/local/google/home/quanw/Code/siemens_2012/Data/SKI10/TrainingData",
|
|
5
|
+
"label_directory": "/usr/local/google/home/quanw/Code/siemens_2012/Data/SKI10/TrainingData_labels",
|
|
6
|
+
"split_file": "/usr/local/google/home/quanw/Code/siemens_2012/experiments/ski10_full_split.json"
|
|
7
|
+
},
|
|
8
|
+
"training_config": {
|
|
9
|
+
"augmentation": true,
|
|
10
|
+
"bone_parameters": {
|
|
11
|
+
"pca_components": 20,
|
|
12
|
+
"subsample_points": 2000
|
|
13
|
+
},
|
|
14
|
+
"cartilage_parameters": {
|
|
15
|
+
"n_estimators": 100,
|
|
16
|
+
"max_depth": 20,
|
|
17
|
+
"subsample_voxels_per_case": 20000,
|
|
18
|
+
"training_proximity_mm": 15.0
|
|
19
|
+
}
|
|
20
|
+
},
|
|
21
|
+
"output_config": {
|
|
22
|
+
"model_directory": "/usr/local/google/home/quanw/Code/siemens_2012/experiments/ski10_full_models_repro"
|
|
23
|
+
}
|
|
24
|
+
}
|