fastMONAI 0.8.3__tar.gz → 0.8.4__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.
- {fastmonai-0.8.3/fastMONAI.egg-info → fastmonai-0.8.4}/PKG-INFO +6 -3
- {fastmonai-0.8.3 → fastmonai-0.8.4}/README.md +3 -0
- fastmonai-0.8.4/fastMONAI/__init__.py +1 -0
- {fastmonai-0.8.3 → fastmonai-0.8.4}/fastMONAI/_modidx.py +22 -1
- {fastmonai-0.8.3 → fastmonai-0.8.4}/fastMONAI/dataset_info.py +29 -28
- {fastmonai-0.8.3 → fastmonai-0.8.4}/fastMONAI/external_data.py +0 -1
- {fastmonai-0.8.3 → fastmonai-0.8.4}/fastMONAI/utils.py +232 -79
- {fastmonai-0.8.3 → fastmonai-0.8.4}/fastMONAI/vision_augmentation.py +126 -9
- {fastmonai-0.8.3 → fastmonai-0.8.4}/fastMONAI/vision_core.py +8 -9
- {fastmonai-0.8.3 → fastmonai-0.8.4}/fastMONAI/vision_inference.py +12 -19
- {fastmonai-0.8.3 → fastmonai-0.8.4}/fastMONAI/vision_loss.py +5 -6
- {fastmonai-0.8.3 → fastmonai-0.8.4}/fastMONAI/vision_metrics.py +4 -10
- {fastmonai-0.8.3 → fastmonai-0.8.4}/fastMONAI/vision_patch.py +65 -23
- {fastmonai-0.8.3 → fastmonai-0.8.4}/fastMONAI/vision_plot.py +1 -8
- {fastmonai-0.8.3 → fastmonai-0.8.4/fastMONAI.egg-info}/PKG-INFO +6 -3
- {fastmonai-0.8.3 → fastmonai-0.8.4}/fastMONAI.egg-info/SOURCES.txt +0 -1
- {fastmonai-0.8.3 → fastmonai-0.8.4}/fastMONAI.egg-info/requires.txt +3 -2
- {fastmonai-0.8.3 → fastmonai-0.8.4}/settings.ini +3 -2
- {fastmonai-0.8.3 → fastmonai-0.8.4}/setup.py +2 -1
- fastmonai-0.8.3/fastMONAI/__init__.py +0 -1
- fastmonai-0.8.3/fastMONAI/research_utils.py +0 -17
- {fastmonai-0.8.3 → fastmonai-0.8.4}/CONTRIBUTING.md +0 -0
- {fastmonai-0.8.3 → fastmonai-0.8.4}/LICENSE +0 -0
- {fastmonai-0.8.3 → fastmonai-0.8.4}/MANIFEST.in +0 -0
- {fastmonai-0.8.3 → fastmonai-0.8.4}/fastMONAI/vision_all.py +0 -0
- {fastmonai-0.8.3 → fastmonai-0.8.4}/fastMONAI/vision_data.py +0 -0
- {fastmonai-0.8.3 → fastmonai-0.8.4}/fastMONAI.egg-info/dependency_links.txt +0 -0
- {fastmonai-0.8.3 → fastmonai-0.8.4}/fastMONAI.egg-info/entry_points.txt +0 -0
- {fastmonai-0.8.3 → fastmonai-0.8.4}/fastMONAI.egg-info/not-zip-safe +0 -0
- {fastmonai-0.8.3 → fastmonai-0.8.4}/fastMONAI.egg-info/top_level.txt +0 -0
- {fastmonai-0.8.3 → fastmonai-0.8.4}/pyproject.toml +0 -0
- {fastmonai-0.8.3 → fastmonai-0.8.4}/setup.cfg +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: fastMONAI
|
|
3
|
-
Version: 0.8.
|
|
3
|
+
Version: 0.8.4
|
|
4
4
|
Summary: fastMONAI library
|
|
5
5
|
Home-page: https://github.com/MMIV-ML/fastMONAI
|
|
6
6
|
Author: Satheshkumar Kaliyugarasan
|
|
@@ -25,8 +25,6 @@ Requires-Dist: imagedata==3.8.14
|
|
|
25
25
|
Requires-Dist: mlflow==3.9.0
|
|
26
26
|
Requires-Dist: huggingface-hub
|
|
27
27
|
Requires-Dist: gdown
|
|
28
|
-
Requires-Dist: gradio
|
|
29
|
-
Requires-Dist: opencv-python
|
|
30
28
|
Requires-Dist: plum-dispatch
|
|
31
29
|
Provides-Extra: dev
|
|
32
30
|
Requires-Dist: ipywidgets; extra == "dev"
|
|
@@ -34,6 +32,8 @@ Requires-Dist: nbdev<3; extra == "dev"
|
|
|
34
32
|
Requires-Dist: execnb<0.2; extra == "dev"
|
|
35
33
|
Requires-Dist: tabulate; extra == "dev"
|
|
36
34
|
Requires-Dist: quarto; extra == "dev"
|
|
35
|
+
Provides-Extra: viz
|
|
36
|
+
Requires-Dist: gradio; extra == "viz"
|
|
37
37
|
Dynamic: author
|
|
38
38
|
Dynamic: author-email
|
|
39
39
|
Dynamic: classifier
|
|
@@ -93,6 +93,9 @@ We recommend using a conda environment to avoid dependency conflicts:
|
|
|
93
93
|
|
|
94
94
|
`pip install fastMONAI`
|
|
95
95
|
|
|
96
|
+
Optional Gradio web-app demo (tutorial 11b):
|
|
97
|
+
`pip install fastMONAI[viz]`.
|
|
98
|
+
|
|
96
99
|
## Development install [(GitHub)](https://github.com/MMIV-ML/fastMONAI)
|
|
97
100
|
|
|
98
101
|
If you want to install an editable version of fastMONAI for development:
|
|
@@ -43,6 +43,9 @@ We recommend using a conda environment to avoid dependency conflicts:
|
|
|
43
43
|
|
|
44
44
|
`pip install fastMONAI`
|
|
45
45
|
|
|
46
|
+
Optional Gradio web-app demo (tutorial 11b):
|
|
47
|
+
`pip install fastMONAI[viz]`.
|
|
48
|
+
|
|
46
49
|
## Development install [(GitHub)](https://github.com/MMIV-ML/fastMONAI)
|
|
47
50
|
|
|
48
51
|
If you want to install an editable version of fastMONAI for development:
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "0.8.4"
|
|
@@ -71,6 +71,8 @@ d = { 'settings': { 'branch': 'main',
|
|
|
71
71
|
'fastMONAI.utils.MLflowUIManager.__init__': ('utils.html#mlflowuimanager.__init__', 'fastMONAI/utils.py'),
|
|
72
72
|
'fastMONAI.utils.MLflowUIManager._find_running_mlflow': ( 'utils.html#mlflowuimanager._find_running_mlflow',
|
|
73
73
|
'fastMONAI/utils.py'),
|
|
74
|
+
'fastMONAI.utils.MLflowUIManager._kill_process': ( 'utils.html#mlflowuimanager._kill_process',
|
|
75
|
+
'fastMONAI/utils.py'),
|
|
74
76
|
'fastMONAI.utils.MLflowUIManager.check_mlflow_installed': ( 'utils.html#mlflowuimanager.check_mlflow_installed',
|
|
75
77
|
'fastMONAI/utils.py'),
|
|
76
78
|
'fastMONAI.utils.MLflowUIManager.find_available_port': ( 'utils.html#mlflowuimanager.find_available_port',
|
|
@@ -112,16 +114,23 @@ d = { 'settings': { 'branch': 'main',
|
|
|
112
114
|
'fastMONAI.utils.ModelTrackingCallback.log_metrics_table': ( 'utils.html#modeltrackingcallback.log_metrics_table',
|
|
113
115
|
'fastMONAI/utils.py'),
|
|
114
116
|
'fastMONAI.utils._detect_patch_workflow': ('utils.html#_detect_patch_workflow', 'fastMONAI/utils.py'),
|
|
117
|
+
'fastMONAI.utils._die_with_parent': ('utils.html#_die_with_parent', 'fastMONAI/utils.py'),
|
|
118
|
+
'fastMONAI.utils._ensure_fastmonai_tracking_uri': ( 'utils.html#_ensure_fastmonai_tracking_uri',
|
|
119
|
+
'fastMONAI/utils.py'),
|
|
115
120
|
'fastMONAI.utils._extract_loss_name': ('utils.html#_extract_loss_name', 'fastMONAI/utils.py'),
|
|
116
121
|
'fastMONAI.utils._extract_model_name': ('utils.html#_extract_model_name', 'fastMONAI/utils.py'),
|
|
117
122
|
'fastMONAI.utils._extract_patch_config': ('utils.html#_extract_patch_config', 'fastMONAI/utils.py'),
|
|
118
123
|
'fastMONAI.utils._extract_size_from_transforms': ( 'utils.html#_extract_size_from_transforms',
|
|
119
124
|
'fastMONAI/utils.py'),
|
|
120
125
|
'fastMONAI.utils._extract_standard_config': ('utils.html#_extract_standard_config', 'fastMONAI/utils.py'),
|
|
126
|
+
'fastMONAI.utils._resolve_backend_store_uri': ( 'utils.html#_resolve_backend_store_uri',
|
|
127
|
+
'fastMONAI/utils.py'),
|
|
128
|
+
'fastMONAI.utils._to_jsonable': ('utils.html#_to_jsonable', 'fastMONAI/utils.py'),
|
|
121
129
|
'fastMONAI.utils.create_mlflow_callback': ('utils.html#create_mlflow_callback', 'fastMONAI/utils.py'),
|
|
122
130
|
'fastMONAI.utils.load_patch_variables': ('utils.html#load_patch_variables', 'fastMONAI/utils.py'),
|
|
123
131
|
'fastMONAI.utils.load_variables': ('utils.html#load_variables', 'fastMONAI/utils.py'),
|
|
124
132
|
'fastMONAI.utils.print_colab_gpu_info': ('utils.html#print_colab_gpu_info', 'fastMONAI/utils.py'),
|
|
133
|
+
'fastMONAI.utils.set_mlflow_tracking_uri': ('utils.html#set_mlflow_tracking_uri', 'fastMONAI/utils.py'),
|
|
125
134
|
'fastMONAI.utils.store_patch_variables': ('utils.html#store_patch_variables', 'fastMONAI/utils.py'),
|
|
126
135
|
'fastMONAI.utils.store_variables': ('utils.html#store_variables', 'fastMONAI/utils.py')},
|
|
127
136
|
'fastMONAI.vision_all': {},
|
|
@@ -171,6 +180,8 @@ d = { 'settings': { 'branch': 'main',
|
|
|
171
180
|
'fastMONAI/vision_augmentation.py'),
|
|
172
181
|
'fastMONAI.vision_augmentation.NormalizeIntensity.tio_transform': ( 'vision_augment.html#normalizeintensity.tio_transform',
|
|
173
182
|
'fastMONAI/vision_augmentation.py'),
|
|
183
|
+
'fastMONAI.vision_augmentation.NormalizeIntensity.to_spec': ( 'vision_augment.html#normalizeintensity.to_spec',
|
|
184
|
+
'fastMONAI/vision_augmentation.py'),
|
|
174
185
|
'fastMONAI.vision_augmentation.OneOf': ( 'vision_augment.html#oneof',
|
|
175
186
|
'fastMONAI/vision_augmentation.py'),
|
|
176
187
|
'fastMONAI.vision_augmentation.OneOf.__init__': ( 'vision_augment.html#oneof.__init__',
|
|
@@ -285,6 +296,8 @@ d = { 'settings': { 'branch': 'main',
|
|
|
285
296
|
'fastMONAI/vision_augmentation.py'),
|
|
286
297
|
'fastMONAI.vision_augmentation.RescaleIntensity.tio_transform': ( 'vision_augment.html#rescaleintensity.tio_transform',
|
|
287
298
|
'fastMONAI/vision_augmentation.py'),
|
|
299
|
+
'fastMONAI.vision_augmentation.RescaleIntensity.to_spec': ( 'vision_augment.html#rescaleintensity.to_spec',
|
|
300
|
+
'fastMONAI/vision_augmentation.py'),
|
|
288
301
|
'fastMONAI.vision_augmentation.ZNormalization': ( 'vision_augment.html#znormalization',
|
|
289
302
|
'fastMONAI/vision_augmentation.py'),
|
|
290
303
|
'fastMONAI.vision_augmentation.ZNormalization.__init__': ( 'vision_augment.html#znormalization.__init__',
|
|
@@ -293,6 +306,8 @@ d = { 'settings': { 'branch': 'main',
|
|
|
293
306
|
'fastMONAI/vision_augmentation.py'),
|
|
294
307
|
'fastMONAI.vision_augmentation.ZNormalization.tio_transform': ( 'vision_augment.html#znormalization.tio_transform',
|
|
295
308
|
'fastMONAI/vision_augmentation.py'),
|
|
309
|
+
'fastMONAI.vision_augmentation.ZNormalization.to_spec': ( 'vision_augment.html#znormalization.to_spec',
|
|
310
|
+
'fastMONAI/vision_augmentation.py'),
|
|
296
311
|
'fastMONAI.vision_augmentation._TioNormalizeIntensity': ( 'vision_augment.html#_tionormalizeintensity',
|
|
297
312
|
'fastMONAI/vision_augmentation.py'),
|
|
298
313
|
'fastMONAI.vision_augmentation._TioNormalizeIntensity.__init__': ( 'vision_augment.html#_tionormalizeintensity.__init__',
|
|
@@ -321,12 +336,18 @@ d = { 'settings': { 'branch': 'main',
|
|
|
321
336
|
'fastMONAI/vision_augmentation.py'),
|
|
322
337
|
'fastMONAI.vision_augmentation._foreground_masking': ( 'vision_augment.html#_foreground_masking',
|
|
323
338
|
'fastMONAI/vision_augmentation.py'),
|
|
339
|
+
'fastMONAI.vision_augmentation.build_transform_from_spec': ( 'vision_augment.html#build_transform_from_spec',
|
|
340
|
+
'fastMONAI/vision_augmentation.py'),
|
|
324
341
|
'fastMONAI.vision_augmentation.do_pad_or_crop': ( 'vision_augment.html#do_pad_or_crop',
|
|
325
342
|
'fastMONAI/vision_augmentation.py'),
|
|
326
343
|
'fastMONAI.vision_augmentation.gpu_patch_augmentations': ( 'vision_augment.html#gpu_patch_augmentations',
|
|
327
344
|
'fastMONAI/vision_augmentation.py'),
|
|
328
345
|
'fastMONAI.vision_augmentation.suggest_patch_augmentations': ( 'vision_augment.html#suggest_patch_augmentations',
|
|
329
|
-
'fastMONAI/vision_augmentation.py')
|
|
346
|
+
'fastMONAI/vision_augmentation.py'),
|
|
347
|
+
'fastMONAI.vision_augmentation.transforms_from_specs': ( 'vision_augment.html#transforms_from_specs',
|
|
348
|
+
'fastMONAI/vision_augmentation.py'),
|
|
349
|
+
'fastMONAI.vision_augmentation.transforms_to_specs': ( 'vision_augment.html#transforms_to_specs',
|
|
350
|
+
'fastMONAI/vision_augmentation.py')},
|
|
330
351
|
'fastMONAI.vision_core': { 'fastMONAI.vision_core.MedBase': ('vision_core.html#medbase', 'fastMONAI/vision_core.py'),
|
|
331
352
|
'fastMONAI.vision_core.MedBase.__copy__': ( 'vision_core.html#medbase.__copy__',
|
|
332
353
|
'fastMONAI/vision_core.py'),
|
|
@@ -61,23 +61,21 @@ class MedDataset:
|
|
|
61
61
|
self.max_workers = max_workers
|
|
62
62
|
self.use_cache = use_cache
|
|
63
63
|
self.cache_path = cache_path
|
|
64
|
+
self.failed_files = []
|
|
64
65
|
self.df = self._create_data_frame()
|
|
65
66
|
|
|
66
67
|
def _create_data_frame(self):
|
|
67
68
|
"""Private method that returns a dataframe with information about the dataset."""
|
|
68
69
|
|
|
69
|
-
# Handle img_list (simple list of paths)
|
|
70
70
|
if self.img_list is not None:
|
|
71
71
|
file_list = self.img_list
|
|
72
72
|
|
|
73
|
-
# Handle path-based initialization
|
|
74
73
|
elif self.path:
|
|
75
74
|
file_list = glob.glob(f'{self.path}/*{self.postfix}*')
|
|
76
75
|
if not file_list:
|
|
77
76
|
print('Could not find images. Check the image path')
|
|
78
77
|
return pd.DataFrame()
|
|
79
78
|
|
|
80
|
-
# Handle dataframe-based initialization
|
|
81
79
|
elif self.input_df is not None and self.mask_col in self.input_df.columns:
|
|
82
80
|
file_list = self.input_df[self.mask_col].tolist()
|
|
83
81
|
|
|
@@ -85,11 +83,9 @@ class MedDataset:
|
|
|
85
83
|
print('Error: Must provide path, img_list, or dataframe with mask_col')
|
|
86
84
|
return pd.DataFrame()
|
|
87
85
|
|
|
88
|
-
# Resolve cache path
|
|
89
86
|
if self.use_cache and self.cache_path is None:
|
|
90
87
|
self.cache_path = self._auto_cache_path(file_list)
|
|
91
88
|
|
|
92
|
-
# Process images to extract metadata (with optional caching)
|
|
93
89
|
if self.use_cache and self.cache_path is not None:
|
|
94
90
|
data_info_dict = self._process_with_cache(file_list)
|
|
95
91
|
else:
|
|
@@ -98,7 +94,19 @@ class MedDataset:
|
|
|
98
94
|
|
|
99
95
|
df = pd.DataFrame(data_info_dict)
|
|
100
96
|
|
|
101
|
-
|
|
97
|
+
# DSET-1: surface failed files instead of letting NaN error rows silently
|
|
98
|
+
# corrupt downstream statistics (summary/get_suggestion/get_size_statistics/...).
|
|
99
|
+
if 'error' in df.columns:
|
|
100
|
+
failed_mask = df['error'].notna()
|
|
101
|
+
self.failed_files = df.loc[failed_mask, 'path'].tolist()
|
|
102
|
+
if self.failed_files:
|
|
103
|
+
warnings.warn(
|
|
104
|
+
f'{len(self.failed_files)}/{len(df)} file(s) failed to load and were '
|
|
105
|
+
f'excluded from dataset statistics. See self.failed_files.'
|
|
106
|
+
)
|
|
107
|
+
df = df.loc[~failed_mask].drop(columns=['error']).reset_index(drop=True)
|
|
108
|
+
|
|
109
|
+
if len(df) > 0 and 'orientation' in df.columns and df.orientation.nunique() > 1 and not self.apply_reorder:
|
|
102
110
|
raise ValueError(
|
|
103
111
|
'Mixed orientations detected in dataset. '
|
|
104
112
|
'Please recreate MedDataset with apply_reorder=True to get correct resample values: '
|
|
@@ -137,6 +145,8 @@ class MedDataset:
|
|
|
137
145
|
|
|
138
146
|
def summary(self):
|
|
139
147
|
"""Summary DataFrame of the dataset with example path for similar data."""
|
|
148
|
+
if len(self.df) == 0:
|
|
149
|
+
raise ValueError("Dataset is empty - cannot summarize")
|
|
140
150
|
|
|
141
151
|
columns = ['dim_0', 'dim_1', 'dim_2', 'voxel_0', 'voxel_1', 'voxel_2', 'orientation']
|
|
142
152
|
|
|
@@ -162,6 +172,8 @@ class MedDataset:
|
|
|
162
172
|
dict: {'target_spacing': [voxel_0, voxel_1, voxel_2]}
|
|
163
173
|
If include_patch_size=True, also includes 'patch_size': [dim_0, dim_1, dim_2]
|
|
164
174
|
"""
|
|
175
|
+
if len(self.df) == 0:
|
|
176
|
+
raise ValueError("Dataset is empty - cannot suggest preprocessing parameters")
|
|
165
177
|
target_spacing = [float(self.df.voxel_0.mode()[0]), float(self.df.voxel_1.mode()[0]), float(self.df.voxel_2.mode()[0])]
|
|
166
178
|
result = {'target_spacing': target_spacing}
|
|
167
179
|
|
|
@@ -186,10 +198,8 @@ class MedDataset:
|
|
|
186
198
|
# Calculate voxel volume in mm³
|
|
187
199
|
voxel_volume = o.spacing[0] * o.spacing[1] * o.spacing[2]
|
|
188
200
|
|
|
189
|
-
# Get voxel counts for each label
|
|
190
201
|
mask_labels_dict = o.count_labels()
|
|
191
202
|
|
|
192
|
-
# Calculate volumes for each label > 0 (skip background)
|
|
193
203
|
for key, voxel_count in mask_labels_dict.items():
|
|
194
204
|
label_int = int(key)
|
|
195
205
|
if label_int > 0 and voxel_count > 0: # Skip background (label 0)
|
|
@@ -285,11 +295,13 @@ class MedDataset:
|
|
|
285
295
|
all_results[i] = info
|
|
286
296
|
|
|
287
297
|
n_cached = len(cached_results)
|
|
288
|
-
|
|
298
|
+
n_failed = sum(1 for info in fresh_results if 'error' in info)
|
|
299
|
+
n_processed = len(files_to_process) - n_failed
|
|
300
|
+
_fail_suffix = f', {n_failed} failed' if n_failed else ''
|
|
289
301
|
if n_cached > 0:
|
|
290
|
-
print(f"MedDataset cache: {n_cached} cached, {n_processed} processed")
|
|
291
|
-
elif n_processed > 0:
|
|
292
|
-
print(f"MedDataset: processed {n_processed} files (results cached)")
|
|
302
|
+
print(f"MedDataset cache: {n_cached} cached, {n_processed} processed{_fail_suffix}")
|
|
303
|
+
elif n_processed > 0 or n_failed:
|
|
304
|
+
print(f"MedDataset: processed {n_processed} files (results cached){_fail_suffix}")
|
|
293
305
|
|
|
294
306
|
return all_results
|
|
295
307
|
|
|
@@ -387,38 +399,37 @@ class MedDataset:
|
|
|
387
399
|
|
|
388
400
|
def _visualize_single_case(self, img_path, mask_path, case_id, anatomical_plane=2, cmap='hot', figsize=(12, 5)):
|
|
389
401
|
"""Helper method to visualize a single case."""
|
|
402
|
+
# Snapshot global MedBase preprocessing state so visualization stays side-effect-free (ARCH-1)
|
|
403
|
+
_saved = (MedBase.target_spacing, MedBase.apply_reorder, MedBase.affine_matrix)
|
|
390
404
|
try:
|
|
391
|
-
# Create MedImage and MedMask with current preprocessing settings
|
|
392
405
|
suggestion = self.get_suggestion()
|
|
393
406
|
MedBase.item_preprocessing(target_spacing=suggestion['target_spacing'], apply_reorder=self.apply_reorder)
|
|
394
407
|
|
|
395
408
|
img = MedImage.create(img_path)
|
|
396
409
|
mask = MedMask.create(mask_path)
|
|
397
410
|
|
|
398
|
-
# Find optimal slice using explicit function
|
|
399
411
|
mask_data = mask.numpy()[0] # Remove channel dimension
|
|
400
412
|
optimal_slice = find_max_slice(mask_data, anatomical_plane)
|
|
401
413
|
|
|
402
|
-
# Create subplot
|
|
403
414
|
fig, axes = plt.subplots(1, 2, figsize=figsize)
|
|
404
415
|
|
|
405
|
-
# Show image
|
|
406
416
|
img.show(ctx=axes[0], anatomical_plane=anatomical_plane, slice_index=optimal_slice)
|
|
407
417
|
axes[0].set_title(f"{case_id} - Image (slice {optimal_slice})")
|
|
408
418
|
|
|
409
|
-
# Show overlay
|
|
410
419
|
img.show(ctx=axes[1], anatomical_plane=anatomical_plane, slice_index=optimal_slice)
|
|
411
420
|
mask.show(ctx=axes[1], anatomical_plane=anatomical_plane, slice_index=optimal_slice,
|
|
412
421
|
alpha=0.3, cmap=cmap)
|
|
413
422
|
axes[1].set_title(f"{case_id} - Overlay (slice {optimal_slice})")
|
|
414
423
|
|
|
415
|
-
# Adjust spacing to bring plots closer
|
|
416
424
|
plt.subplots_adjust(wspace=0.1)
|
|
417
425
|
plt.tight_layout()
|
|
418
426
|
plt.show()
|
|
419
427
|
|
|
420
428
|
except Exception as e:
|
|
421
429
|
print(f"Failed to visualize case {case_id}: {e}")
|
|
430
|
+
finally:
|
|
431
|
+
# Restore global state: visualize_cases() must not mutate training preprocessing config
|
|
432
|
+
MedBase.target_spacing, MedBase.apply_reorder, MedBase.affine_matrix = _saved
|
|
422
433
|
|
|
423
434
|
def visualize_cases(self, n_cases=4, anatomical_plane=2, cmap='hot', figsize=(12, 5)):
|
|
424
435
|
"""Visualize cases from the dataset.
|
|
@@ -437,7 +448,6 @@ class MedDataset:
|
|
|
437
448
|
print("Error: No image_col specified. Cannot visualize cases.")
|
|
438
449
|
return
|
|
439
450
|
|
|
440
|
-
# Check if required columns exist
|
|
441
451
|
if self.image_col not in self.input_df.columns:
|
|
442
452
|
print(f"Error: Column '{self.image_col}' not found in dataframe.")
|
|
443
453
|
return
|
|
@@ -496,21 +506,17 @@ def suggest_patch_size(
|
|
|
496
506
|
>>> # Use custom spacing
|
|
497
507
|
>>> patch_size = suggest_patch_size(dataset, target_spacing=[1.0, 1.0, 2.0])
|
|
498
508
|
"""
|
|
499
|
-
# Defaults
|
|
500
509
|
min_patch_size = min_patch_size or [32, 32, 32]
|
|
501
510
|
max_patch_size = max_patch_size or [256, 256, 256]
|
|
502
511
|
|
|
503
|
-
# Use explicit spacing or get from dataset suggestion
|
|
504
512
|
if target_spacing is None:
|
|
505
513
|
suggestion = dataset.get_suggestion()
|
|
506
514
|
target_spacing = suggestion['target_spacing']
|
|
507
515
|
|
|
508
|
-
# Get size statistics (resampled to target_spacing)
|
|
509
516
|
stats = dataset.get_size_statistics(target_spacing)
|
|
510
517
|
median_shape = stats['median']
|
|
511
518
|
min_shape = stats['min']
|
|
512
519
|
|
|
513
|
-
# Handle single-image edge case
|
|
514
520
|
if len(dataset.df) == 1:
|
|
515
521
|
warnings.warn("Single image dataset - using image dimensions directly")
|
|
516
522
|
|
|
@@ -521,10 +527,8 @@ def suggest_patch_size(
|
|
|
521
527
|
# Step 1: Clamp to min(median, min_volume) per axis — safety guarantee
|
|
522
528
|
effective_dims = [min(med, mn) for med, mn in zip(median_shape, min_shape)]
|
|
523
529
|
|
|
524
|
-
# Step 2: Round down to nearest divisor
|
|
525
530
|
patch_size = [round_to_divisor(dim, divisor) for dim in effective_dims]
|
|
526
531
|
|
|
527
|
-
# Step 3: Clamp to [min_patch_size, max_patch_size] bounds
|
|
528
532
|
patch_size = [
|
|
529
533
|
max(min_p, min(max_p, p))
|
|
530
534
|
for p, min_p, max_p in zip(patch_size, min_patch_size, max_patch_size)
|
|
@@ -577,7 +581,6 @@ def preprocess_dataset(df, img_col, mask_col=None, output_dir='preprocessed',
|
|
|
577
581
|
volume into memory, so reduce for large volumes.
|
|
578
582
|
skip_existing: Skip files that already exist on disk (with size > 0).
|
|
579
583
|
"""
|
|
580
|
-
# Input validation
|
|
581
584
|
if len(df) == 0:
|
|
582
585
|
raise ValueError("DataFrame is empty")
|
|
583
586
|
if img_col not in df.columns:
|
|
@@ -608,7 +611,6 @@ def preprocess_dataset(df, img_col, mask_col=None, output_dir='preprocessed',
|
|
|
608
611
|
all_tfms.extend([getattr(t, 'tio_transform', t) for t in transforms])
|
|
609
612
|
pipeline = tio.Compose(all_tfms) if all_tfms else None
|
|
610
613
|
|
|
611
|
-
# Create output directories
|
|
612
614
|
output_dir = Path(output_dir)
|
|
613
615
|
img_dir = output_dir / 'images'
|
|
614
616
|
img_dir.mkdir(parents=True, exist_ok=True)
|
|
@@ -638,7 +640,6 @@ def preprocess_dataset(df, img_col, mask_col=None, output_dir='preprocessed',
|
|
|
638
640
|
'out_img': out_img, 'out_mask': out_mask,
|
|
639
641
|
})
|
|
640
642
|
|
|
641
|
-
# Process cases
|
|
642
643
|
processed = 0
|
|
643
644
|
failed = 0
|
|
644
645
|
failed_cases = []
|
|
@@ -88,7 +88,6 @@ def download_ixi_data(path: (str, Path) = '../data') -> Path:
|
|
|
88
88
|
path = Path(path) / 'IXI'
|
|
89
89
|
img_path = path / 'T1_images'
|
|
90
90
|
|
|
91
|
-
# Check whether image data already present in img_path:
|
|
92
91
|
is_extracted = False
|
|
93
92
|
try:
|
|
94
93
|
if len(list(img_path.iterdir())) >= 581: # 581 imgs in the IXI dataset
|