Skip to content
Merged

Dev #66

Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion fastMONAI/__init__.py
Original file line number Diff line number Diff line change
@@ -1 +1 @@
__version__ = "0.8.0"
__version__ = "0.8.1"
2 changes: 2 additions & 0 deletions fastMONAI/_modidx.py
Original file line number Diff line number Diff line change
Expand Up @@ -291,6 +291,8 @@
'fastMONAI/vision_augmentation.py'),
'fastMONAI.vision_augmentation._create_ellipsoid_mask': ( 'vision_augment.html#_create_ellipsoid_mask',
'fastMONAI/vision_augmentation.py'),
'fastMONAI.vision_augmentation._foreground_masking': ( 'vision_augment.html#_foreground_masking',
'fastMONAI/vision_augmentation.py'),
'fastMONAI.vision_augmentation.do_pad_or_crop': ( 'vision_augment.html#do_pad_or_crop',
'fastMONAI/vision_augmentation.py'),
'fastMONAI.vision_augmentation.suggest_patch_augmentations': ( 'vision_augment.html#suggest_patch_augmentations',
Expand Down
7 changes: 7 additions & 0 deletions fastMONAI/vision_augmentation.py
Original file line number Diff line number Diff line change
Expand Up @@ -91,13 +91,20 @@ def tio_transform(self):
def encodes(self, o: (MedImage, MedMask)):
return type(o)(self.pad_or_crop(o))

# %% ../nbs/03_vision_augment.ipynb #534509q2nn
def _foreground_masking(tensor):
"""Mask for non-zero voxels (nnU-Net-style foreground normalization)."""
return tensor > 0

# %% ../nbs/03_vision_augment.ipynb #ca95a690
class ZNormalization(DisplayedTransform):
"""Apply TorchIO `ZNormalization`."""

order = 0

def __init__(self, masking_method=None, channel_wise=True):
if masking_method == 'foreground':
masking_method = _foreground_masking
self.z_normalization = tio.ZNormalization(masking_method=masking_method)
self.channel_wise = channel_wise

Expand Down
55 changes: 9 additions & 46 deletions nbs/03_vision_augment.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -157,58 +157,21 @@
" return type(o)(self.pad_or_crop(o))"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "534509q2nn",
"metadata": {},
"outputs": [],
"source": "#| export\ndef _foreground_masking(tensor):\n \"\"\"Mask for non-zero voxels (nnU-Net-style foreground normalization).\"\"\"\n return tensor > 0"
},
{
"cell_type": "code",
"execution_count": null,
"id": "ca95a690",
"metadata": {},
"outputs": [],
"source": [
"# | export\n",
"class ZNormalization(DisplayedTransform):\n",
" \"\"\"Apply TorchIO `ZNormalization`.\"\"\"\n",
"\n",
" order = 0\n",
"\n",
" def __init__(self, masking_method=None, channel_wise=True):\n",
" self.z_normalization = tio.ZNormalization(masking_method=masking_method)\n",
" self.channel_wise = channel_wise\n",
"\n",
" @property\n",
" def tio_transform(self):\n",
" \"\"\"Return the underlying TorchIO transform.\"\"\"\n",
" return self.z_normalization\n",
"\n",
" def encodes(self, o: MedImage):\n",
" try:\n",
" if self.channel_wise:\n",
" o = torch.stack([self.z_normalization(c[None])[0] for c in o])\n",
" else: \n",
" o = self.z_normalization(o)\n",
" except RuntimeError as e:\n",
" if \"Standard deviation is 0\" in str(e):\n",
" # Calculate mean for debugging information\n",
" mean = float(o.mean())\n",
" \n",
" error_msg = (\n",
" f\"Standard deviation is 0 for image (mean={mean:.3f}).\\n\"\n",
" f\"This indicates uniform pixel values.\\n\\n\"\n",
" f\"Possible causes:\\n\"\n",
" f\"• Corrupted or blank image\\n\"\n",
" f\"• Oversaturated regions\\n\" \n",
" f\"• Background-only regions\\n\"\n",
" f\"• All-zero mask being processed as image\\n\\n\"\n",
" f\"Suggested solutions:\\n\"\n",
" f\"• Check image quality and acquisition\\n\"\n",
" f\"• Verify image vs mask data loading\"\n",
" )\n",
" raise RuntimeError(error_msg) from e\n",
"\n",
" return MedImage.create(o)\n",
"\n",
" def encodes(self, o: MedMask):\n",
" return o"
]
"source": "# | export\nclass ZNormalization(DisplayedTransform):\n \"\"\"Apply TorchIO `ZNormalization`.\"\"\"\n\n order = 0\n\n def __init__(self, masking_method=None, channel_wise=True):\n if masking_method == 'foreground':\n masking_method = _foreground_masking\n self.z_normalization = tio.ZNormalization(masking_method=masking_method)\n self.channel_wise = channel_wise\n\n @property\n def tio_transform(self):\n \"\"\"Return the underlying TorchIO transform.\"\"\"\n return self.z_normalization\n\n def encodes(self, o: MedImage):\n try:\n if self.channel_wise:\n o = torch.stack([self.z_normalization(c[None])[0] for c in o])\n else: \n o = self.z_normalization(o)\n except RuntimeError as e:\n if \"Standard deviation is 0\" in str(e):\n # Calculate mean for debugging information\n mean = float(o.mean())\n \n error_msg = (\n f\"Standard deviation is 0 for image (mean={mean:.3f}).\\n\"\n f\"This indicates uniform pixel values.\\n\\n\"\n f\"Possible causes:\\n\"\n f\"• Corrupted or blank image\\n\"\n f\"• Oversaturated regions\\n\" \n f\"• Background-only regions\\n\"\n f\"• All-zero mask being processed as image\\n\\n\"\n f\"Suggested solutions:\\n\"\n f\"• Check image quality and acquisition\\n\"\n f\"• Verify image vs mask data loading\"\n )\n raise RuntimeError(error_msg) from e\n\n return MedImage.create(o)\n\n def encodes(self, o: MedMask):\n return o"
},
{
"cell_type": "code",
Expand Down
368 changes: 225 additions & 143 deletions nbs/12a_tutorial_patch_training.ipynb

Large diffs are not rendered by default.

34 changes: 31 additions & 3 deletions nbs/12b_tutorial_patch_inference.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -258,15 +258,43 @@
"cell_type": "markdown",
"id": "cell-model-header",
"metadata": {},
"source": "### Load trained model\n\nLoad the exported learner which contains both the model architecture and best weights.\n\n**Two options:**\n1. **Local file**: Load `best_learner.pkl` exported during training\n2. **MLflow**: Download from MLflow artifacts (for experiment tracking workflows)"
"source": [
"### Load trained model\n",
"\n",
"Load the exported learner which contains both the model architecture and best weights.\n",
"\n",
"**Two options:**\n",
"1. **Local file**: Load `best_learner.pkl` exported during training\n",
"2. **MLflow**: Download from MLflow artifacts (for experiment tracking workflows)"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "cell-learner",
"metadata": {},
"outputs": [],
"source": "from fastai.learner import load_learner\nimport torch\n\n# Option 1: Load from local file\nlearn = load_learner('models/best_learner.pkl')\n\n# Option 2: Load from MLflow (uncomment to use)\n# import mlflow\n# run_id = \"your_run_id\" # Get from MLflow UI\n# mlflow.artifacts.download_artifacts(run_id=run_id, artifact_path=\"model/best_learner.pkl\", dst_path=\"./\")\n# learn = load_learner('best_learner.pkl')\n\nmodel = learn.model\ndevice = torch.device('cuda' if torch.cuda.is_available() else 'cpu')\nmodel.to(device)\nmodel.eval()\n\nprint(f\"Loaded model: {model.__class__.__name__}\")\nprint(f\"Device: {device}\")"
"source": [
"from fastai.learner import load_learner\n",
"import torch\n",
"\n",
"# Option 1: Load from local file\n",
"learn = load_learner('models/best_learner.pkl')\n",
"\n",
"# Option 2: Load from MLflow (uncomment to use)\n",
"# import mlflow\n",
"# run_id = \"your_run_id\" # Get from MLflow UI\n",
"# mlflow.artifacts.download_artifacts(run_id=run_id, artifact_path=\"model/best_learner.pkl\", dst_path=\"./\")\n",
"# learn = load_learner('best_learner.pkl')\n",
"\n",
"model = learn.model\n",
"device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')\n",
"model.to(device)\n",
"model.eval()\n",
"\n",
"print(f\"Loaded model: {model.__class__.__name__}\")\n",
"print(f\"Device: {device}\")"
]
},
{
"cell_type": "markdown",
Expand All @@ -288,7 +316,7 @@
"outputs": [],
"source": [
"# MUST match training pre_patch_tfms\n",
"pre_inference_tfms = [ZNormalization()]"
"pre_inference_tfms = [ZNormalization(masking_method='foreground')]\n"
]
},
{
Expand Down
4 changes: 3 additions & 1 deletion nbs/sidebar.yml
Original file line number Diff line number Diff line change
Expand Up @@ -20,4 +20,6 @@ website:
- 11c_tutorial_regression.ipynb
- 11d_tutorial_binary_segmentation.ipynb
- 11e_tutorial_multiclass_segmentation.ipynb
- 11f_tutorial_inference.ipynb
- 11f_tutorial_inference.ipynb
- 12a_tutorial_patch_training.ipynb
- 12b_tutorial_patch_inference.ipynb
2 changes: 1 addition & 1 deletion settings.ini
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
### Python Library ###
lib_name = fastMONAI
min_python = 3.10
version = 0.8.0
version = 0.8.1
### OPTIONAL ###

requirements = fastai==2.8.6 monai==1.5.2 torchio==0.21.2 xlrd>=1.2.0 scikit-image==0.26.0 imagedata==3.8.14 mlflow==3.9.0 huggingface-hub gdown gradio opencv-python plum-dispatch
Expand Down