fastMONAI 0.8.2__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.
Files changed (32) hide show
  1. {fastmonai-0.8.2/fastMONAI.egg-info → fastmonai-0.8.4}/PKG-INFO +7 -3
  2. {fastmonai-0.8.2 → fastmonai-0.8.4}/README.md +3 -0
  3. fastmonai-0.8.4/fastMONAI/__init__.py +1 -0
  4. {fastmonai-0.8.2 → fastmonai-0.8.4}/fastMONAI/_modidx.py +43 -1
  5. {fastmonai-0.8.2 → fastmonai-0.8.4}/fastMONAI/dataset_info.py +29 -28
  6. {fastmonai-0.8.2 → fastmonai-0.8.4}/fastMONAI/external_data.py +0 -1
  7. {fastmonai-0.8.2 → fastmonai-0.8.4}/fastMONAI/utils.py +232 -79
  8. {fastmonai-0.8.2 → fastmonai-0.8.4}/fastMONAI/vision_augmentation.py +126 -9
  9. {fastmonai-0.8.2 → fastmonai-0.8.4}/fastMONAI/vision_core.py +8 -9
  10. {fastmonai-0.8.2 → fastmonai-0.8.4}/fastMONAI/vision_inference.py +12 -19
  11. {fastmonai-0.8.2 → fastmonai-0.8.4}/fastMONAI/vision_loss.py +7 -7
  12. {fastmonai-0.8.2 → fastmonai-0.8.4}/fastMONAI/vision_metrics.py +4 -10
  13. {fastmonai-0.8.2 → fastmonai-0.8.4}/fastMONAI/vision_patch.py +458 -433
  14. {fastmonai-0.8.2 → fastmonai-0.8.4}/fastMONAI/vision_plot.py +1 -8
  15. {fastmonai-0.8.2 → fastmonai-0.8.4/fastMONAI.egg-info}/PKG-INFO +7 -3
  16. {fastmonai-0.8.2 → fastmonai-0.8.4}/fastMONAI.egg-info/SOURCES.txt +0 -1
  17. {fastmonai-0.8.2 → fastmonai-0.8.4}/fastMONAI.egg-info/requires.txt +4 -2
  18. {fastmonai-0.8.2 → fastmonai-0.8.4}/settings.ini +4 -3
  19. {fastmonai-0.8.2 → fastmonai-0.8.4}/setup.py +2 -1
  20. fastmonai-0.8.2/fastMONAI/__init__.py +0 -1
  21. fastmonai-0.8.2/fastMONAI/research_utils.py +0 -17
  22. {fastmonai-0.8.2 → fastmonai-0.8.4}/CONTRIBUTING.md +0 -0
  23. {fastmonai-0.8.2 → fastmonai-0.8.4}/LICENSE +0 -0
  24. {fastmonai-0.8.2 → fastmonai-0.8.4}/MANIFEST.in +0 -0
  25. {fastmonai-0.8.2 → fastmonai-0.8.4}/fastMONAI/vision_all.py +0 -0
  26. {fastmonai-0.8.2 → fastmonai-0.8.4}/fastMONAI/vision_data.py +0 -0
  27. {fastmonai-0.8.2 → fastmonai-0.8.4}/fastMONAI.egg-info/dependency_links.txt +0 -0
  28. {fastmonai-0.8.2 → fastmonai-0.8.4}/fastMONAI.egg-info/entry_points.txt +0 -0
  29. {fastmonai-0.8.2 → fastmonai-0.8.4}/fastMONAI.egg-info/not-zip-safe +0 -0
  30. {fastmonai-0.8.2 → fastmonai-0.8.4}/fastMONAI.egg-info/top_level.txt +0 -0
  31. {fastmonai-0.8.2 → fastmonai-0.8.4}/pyproject.toml +0 -0
  32. {fastmonai-0.8.2 → 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.2
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,14 +25,15 @@ 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"
33
31
  Requires-Dist: nbdev<3; extra == "dev"
32
+ Requires-Dist: execnb<0.2; extra == "dev"
34
33
  Requires-Dist: tabulate; extra == "dev"
35
34
  Requires-Dist: quarto; extra == "dev"
35
+ Provides-Extra: viz
36
+ Requires-Dist: gradio; extra == "viz"
36
37
  Dynamic: author
37
38
  Dynamic: author-email
38
39
  Dynamic: classifier
@@ -92,6 +93,9 @@ We recommend using a conda environment to avoid dependency conflicts:
92
93
 
93
94
  `pip install fastMONAI`
94
95
 
96
+ Optional Gradio web-app demo (tutorial 11b):
97
+ `pip install fastMONAI[viz]`.
98
+
95
99
  ## Development install [(GitHub)](https://github.com/MMIV-ML/fastMONAI)
96
100
 
97
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'),
@@ -481,6 +502,8 @@ d = { 'settings': { 'branch': 'main',
481
502
  'fastMONAI/vision_patch.py'),
482
503
  'fastMONAI.vision_patch.MedPatchDataLoader.__len__': ( 'vision_patch.html#medpatchdataloader.__len__',
483
504
  'fastMONAI/vision_patch.py'),
505
+ 'fastMONAI.vision_patch.MedPatchDataLoader._apply_patch_tfms': ( 'vision_patch.html#medpatchdataloader._apply_patch_tfms',
506
+ 'fastMONAI/vision_patch.py'),
484
507
  'fastMONAI.vision_patch.MedPatchDataLoader.close': ( 'vision_patch.html#medpatchdataloader.close',
485
508
  'fastMONAI/vision_patch.py'),
486
509
  'fastMONAI.vision_patch.MedPatchDataLoader.dataset': ( 'vision_patch.html#medpatchdataloader.dataset',
@@ -553,18 +576,37 @@ d = { 'settings': { 'branch': 'main',
553
576
  'fastMONAI/vision_patch.py'),
554
577
  'fastMONAI.vision_patch.PatchInferenceEngine.__init__': ( 'vision_patch.html#patchinferenceengine.__init__',
555
578
  'fastMONAI/vision_patch.py'),
579
+ 'fastMONAI.vision_patch.PatchInferenceEngine._postprocess': ( 'vision_patch.html#patchinferenceengine._postprocess',
580
+ 'fastMONAI/vision_patch.py'),
581
+ 'fastMONAI.vision_patch.PatchInferenceEngine._prepare_subject': ( 'vision_patch.html#patchinferenceengine._prepare_subject',
582
+ 'fastMONAI/vision_patch.py'),
583
+ 'fastMONAI.vision_patch.PatchInferenceEngine._run_inference': ( 'vision_patch.html#patchinferenceengine._run_inference',
584
+ 'fastMONAI/vision_patch.py'),
556
585
  'fastMONAI.vision_patch.PatchInferenceEngine.predict': ( 'vision_patch.html#patchinferenceengine.predict',
557
586
  'fastMONAI/vision_patch.py'),
558
587
  'fastMONAI.vision_patch.PatchInferenceEngine.to': ( 'vision_patch.html#patchinferenceengine.to',
559
588
  'fastMONAI/vision_patch.py'),
589
+ 'fastMONAI.vision_patch._PreparedSubject': ( 'vision_patch.html#_preparedsubject',
590
+ 'fastMONAI/vision_patch.py'),
591
+ 'fastMONAI.vision_patch._build_pre_patch_tfms': ( 'vision_patch.html#_build_pre_patch_tfms',
592
+ 'fastMONAI/vision_patch.py'),
560
593
  'fastMONAI.vision_patch._extract_tio_transform': ( 'vision_patch.html#_extract_tio_transform',
561
594
  'fastMONAI/vision_patch.py'),
562
595
  'fastMONAI.vision_patch._get_default_device': ( 'vision_patch.html#_get_default_device',
563
596
  'fastMONAI/vision_patch.py'),
597
+ 'fastMONAI.vision_patch._logits_to_probs': ( 'vision_patch.html#_logits_to_probs',
598
+ 'fastMONAI/vision_patch.py'),
564
599
  'fastMONAI.vision_patch._normalize_patch_overlap': ( 'vision_patch.html#_normalize_patch_overlap',
565
600
  'fastMONAI/vision_patch.py'),
601
+ 'fastMONAI.vision_patch._predict_one': ( 'vision_patch.html#_predict_one',
602
+ 'fastMONAI/vision_patch.py'),
566
603
  'fastMONAI.vision_patch._predict_patch_tta': ( 'vision_patch.html#_predict_patch_tta',
567
604
  'fastMONAI/vision_patch.py'),
605
+ 'fastMONAI.vision_patch._save_prediction': ( 'vision_patch.html#_save_prediction',
606
+ 'fastMONAI/vision_patch.py'),
607
+ 'fastMONAI.vision_patch._split_df': ('vision_patch.html#_split_df', 'fastMONAI/vision_patch.py'),
608
+ 'fastMONAI.vision_patch._stash_from_df_metadata': ( 'vision_patch.html#_stash_from_df_metadata',
609
+ 'fastMONAI/vision_patch.py'),
568
610
  'fastMONAI.vision_patch._warn_config_override': ( 'vision_patch.html#_warn_config_override',
569
611
  'fastMONAI/vision_patch.py'),
570
612
  'fastMONAI.vision_patch.create_patch_sampler': ( 'vision_patch.html#create_patch_sampler',
@@ -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
- if len(df) > 0 and df.orientation.nunique() > 1 and not self.apply_reorder:
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
- n_processed = len(files_to_process)
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