From c2e45f33b6034b95be6abc1ec60adbc5340256c3 Mon Sep 17 00:00:00 2001 From: Suraj Date: Fri, 10 Dec 2021 03:42:17 +0100 Subject: [PATCH 1/4] Add demo for 2d inference on 3D volume Add demo for 2d inference on 3D volume Signed-off-by: Suraj Pai --- README.md | 5 + figures/2d_inference_3d_input.png | Bin 0 -> 18411 bytes modules/2d_inference_3d_volume.ipynb | 201 +++++++++++++++++++++++++++ runner.sh | 1 + 4 files changed, 207 insertions(+) create mode 100644 figures/2d_inference_3d_input.png create mode 100644 modules/2d_inference_3d_volume.ipynb diff --git a/README.md b/README.md index 508a0fff81..3b312844d9 100644 --- a/README.md +++ b/README.md @@ -176,6 +176,11 @@ Training and evaluation examples of 3D segmentation based on UNet3D and syntheti The examples are built with MONAI workflows, mainly contain: trainer/evaluator, handlers, post_transforms, etc. #### [3d_image_transforms](./modules/3d_image_transforms.ipynb) This notebook demonstrates the transformations on volumetric images. + +#### [2d_inference_3d_volume](./modules/2d_inference_3d_volume.ipynb) +Tutorial that demonstrates how monai `SlidingWindowInferer` can be used when a 3D volume input needs to be provided slice-by-slice to a 2D model and finally, aggregated into a 3D volume. + + #### [autoencoder_mednist](./modules/autoencoder_mednist.ipynb) This tutorial uses the MedNIST hand CT scan dataset to demonstrate MONAI's autoencoder class. The autoencoder is used with an identity encode/decode (i.e., what you put in is what you should get back), as well as demonstrating its usage for de-blurring and de-noising. #### [batch_output_transform](./modules/batch_output_transform.py) diff --git a/figures/2d_inference_3d_input.png b/figures/2d_inference_3d_input.png new file mode 100644 index 0000000000000000000000000000000000000000..d344079dea90b60dbccf892541189257e94cdf54 GIT binary patch literal 18411 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 Hf)f7)_6^t& literal 0 HcmV?d00001 diff --git a/modules/2d_inference_3d_volume.ipynb b/modules/2d_inference_3d_volume.ipynb new file mode 100644 index 0000000000..5f62a23811 --- /dev/null +++ b/modules/2d_inference_3d_volume.ipynb @@ -0,0 +1,201 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "c408367e", + "metadata": {}, + "source": [ + "# 2D Model Inference on a 3D Volume " + ] + }, + { + "cell_type": "markdown", + "id": "a8681db2", + "metadata": {}, + "source": [ + "Usecase: A 2D Model, such as, a 2D segmentation U-Net operates on 2D input which can be slices from a 3D volume (for example, a CT scan). \n", + "\n", + "After editing sliding window inferer as described in this tutorial, it can handle the entire flow as shown:\n", + "![image](../figures/2d_inference_3d_input.png)\n", + "\n", + "The input is a *3D Volume*, a *2D model* and the output is a *3D volume* with 2D slice predictions aggregated. \n", + "\n" + ] + }, + { + "cell_type": "markdown", + "id": "239b0d93", + "metadata": {}, + "source": [ + "[![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/Project-MONAI/tutorials/blob/master/modules/2d_inference_3d_volume.ipynb)\n" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "id": "f2e1b91f", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Requirement already satisfied: monai in /home/suraj/miniconda3/envs/monai/lib/python3.7/site-packages (0.8.0)\n", + "Requirement already satisfied: numpy>=1.17 in /home/suraj/miniconda3/envs/monai/lib/python3.7/site-packages (from monai) (1.21.4)\n", + "Requirement already satisfied: torch>=1.6 in /home/suraj/miniconda3/envs/monai/lib/python3.7/site-packages (from monai) (1.10.0)\n", + "Requirement already satisfied: typing-extensions in /home/suraj/miniconda3/envs/monai/lib/python3.7/site-packages (from torch>=1.6->monai) (4.0.1)\n" + ] + } + ], + "source": [ + "# Install monai\n", + "!pip install monai" + ] + }, + { + "cell_type": "markdown", + "id": "85f00a47", + "metadata": {}, + "source": [ + "## Overiding SlidingWindowInferer\n", + "The simplest way to achieve this functionality is to create a class `YourSlidingWindowInferer` that inherits from `SlidingWindowInferer` in `monai.inferers`" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "01f8bfa3", + "metadata": {}, + "outputs": [], + "source": [ + "from monai.inferers import SlidingWindowInferer\n", + "import torch\n", + "from typing import Callable, Any\n", + "\n", + "\n", + "class YourSlidingWindowInferer(SlidingWindowInferer):\n", + " def __init__(self, *args, **kwargs):\n", + " super().__init__(*args, **kwargs)\n", + "\n", + " def __call__(\n", + " self,\n", + " inputs: torch.Tensor,\n", + " network: Callable[..., torch.Tensor],\n", + " *args: Any,\n", + " **kwargs: Any,\n", + " ) -> torch.Tensor:\n", + "\n", + " # Check if roi size (eg. 2D roi) and input volume sizes (3D input) mismatch \n", + " if len(self.roi_size) != len(inputs.shape[2:]):\n", + "\n", + " # If they mismatch and roi_size is 2D add another dimension to roi size\n", + " if len(self.roi_size) == 2:\n", + " self.roi_size = [1, *self.roi_size]\n", + " else:\n", + " raise RuntimeError(\"Unsupported roi size, cannot broadcast to volume. \")\n", + "\n", + " return super().__call__(inputs, lambda x: self.network_wrapper(network, x))\n", + "\n", + " def network_wrapper(self, network, x, *args, **kwargs):\n", + " \"\"\"\n", + " Wrapper handles cases where inference needs to be done using \n", + " 2D models over 3D volume inputs.\n", + " \"\"\"\n", + " # If depth dim is 1 in [D, H, W] roi size, then the input is 2D and needs\n", + " # be handled accordingly\n", + " if self.roi_size[0] == 1:\n", + " # Pass [N, C, H, W] to the model as it is 2D.\n", + " x = x.squeeze(dim=2)\n", + " out = network(x, *args, **kwargs)\n", + " # Unsqueeze the network output so it is [N, C, D, H, W] as expected by the default SlidingWindowInferer class\n", + " return out.unsqueeze(dim=2)\n", + "\n", + " else:\n", + " return network(x, *args, **kwargs)" + ] + }, + { + "cell_type": "markdown", + "id": "bb0a63dd", + "metadata": {}, + "source": [ + "## Testing added functionality\n", + "Let's use the `YourSlidingWindowInferer` in a dummy example to execute the workflow described above." + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "85b15305", + "metadata": {}, + "outputs": [], + "source": [ + "# Create a 2D UNet with randomly initialized weights for testing purposes\n", + "from monai.networks.nets import UNet\n", + "\n", + "# 3 layer network with down/upsampling by a factor of 2 at each layer with 2-convolution residual units\n", + "net = UNet(\n", + " spatial_dims=2,\n", + " in_channels=1,\n", + " out_channels=1,\n", + " channels=(4, 8, 16),\n", + " strides=(2, 2),\n", + " num_res_units=2\n", + ")\n", + "\n", + "# Initialize a dummy 3D tensor volume with shape (N,C,D,H,W)\n", + "input_volume = torch.ones(1, 1, 30, 256, 256)\n", + "\n", + "# Create an instance of YourSlidingWindowInferer with roi_size as the 256x256 (HxW)\n", + "inferer = YourSlidingWindowInferer(roi_size=(256, 256),\n", + " sw_batch_size=1,\n", + " cval=-1)\n", + "\n", + "output = inferer(input_volume, net)" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "5ad96534", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "torch.Size([1, 1, 30, 256, 256])" + ] + }, + "execution_count": 4, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# Output is a 3D volume with 2D slices aggregated\n", + "output.shape" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "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.7.11" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/runner.sh b/runner.sh index 3ed50faca2..96c46fd796 100755 --- a/runner.sh +++ b/runner.sh @@ -36,6 +36,7 @@ doesnt_contain_max_epochs=("${doesnt_contain_max_epochs[@]}" UNet_input_size_con doesnt_contain_max_epochs=("${doesnt_contain_max_epochs[@]}" network_api.ipynb) doesnt_contain_max_epochs=("${doesnt_contain_max_epochs[@]}" tcia_csv_processing.ipynb) doesnt_contain_max_epochs=("${doesnt_contain_max_epochs[@]}" transform_visualization.ipynb) +doesnt_contain_max_epochs=("${doesnt_contain_max_epochs[@]}" 2d_inference_3d_volume.ipynb) # output formatting separator="" From e463f9cb7fc81c8bf83fc8a35457b7775b63df05 Mon Sep 17 00:00:00 2001 From: Suraj Date: Sat, 11 Dec 2021 21:18:10 +0100 Subject: [PATCH 2/4] Add slice_axis to SlidingWindowInferer Signed-off-by: Suraj --- modules/2d_inference_3d_volume.ipynb | 78 +++++++++++++++++----------- 1 file changed, 47 insertions(+), 31 deletions(-) diff --git a/modules/2d_inference_3d_volume.ipynb b/modules/2d_inference_3d_volume.ipynb index 5f62a23811..2192e74e35 100644 --- a/modules/2d_inference_3d_volume.ipynb +++ b/modules/2d_inference_3d_volume.ipynb @@ -74,13 +74,19 @@ "\n", "\n", "class YourSlidingWindowInferer(SlidingWindowInferer):\n", - " def __init__(self, *args, **kwargs):\n", + " def __init__(self, slice_axis: int = 0, \n", + " *args, **kwargs):\n", + " # Set axis to slice the volume across, for example, `0` could slide over axial slices, `1` over coronal slices \n", + " # and `2` over sagittal slices.\n", + " self.slice_axis = slice_axis\n", + "\n", " super().__init__(*args, **kwargs)\n", "\n", " def __call__(\n", " self,\n", " inputs: torch.Tensor,\n", " network: Callable[..., torch.Tensor],\n", + " slice_axis: int = 0,\n", " *args: Any,\n", " **kwargs: Any,\n", " ) -> torch.Tensor:\n", @@ -90,10 +96,11 @@ "\n", " # If they mismatch and roi_size is 2D add another dimension to roi size\n", " if len(self.roi_size) == 2:\n", - " self.roi_size = [1, *self.roi_size]\n", + " self.roi_size = list(self.roi_size)\n", + " self.roi_size.insert(self.slice_axis, 1)\n", " else:\n", - " raise RuntimeError(\"Unsupported roi size, cannot broadcast to volume. \")\n", - "\n", + " raise RuntimeError(\"Currently, only 2D `roi_size` is supported, cannot broadcast to volume. \")\n", + " \n", " return super().__call__(inputs, lambda x: self.network_wrapper(network, x))\n", "\n", " def network_wrapper(self, network, x, *args, **kwargs):\n", @@ -103,12 +110,13 @@ " \"\"\"\n", " # If depth dim is 1 in [D, H, W] roi size, then the input is 2D and needs\n", " # be handled accordingly\n", - " if self.roi_size[0] == 1:\n", - " # Pass [N, C, H, W] to the model as it is 2D.\n", - " x = x.squeeze(dim=2)\n", + "\n", + " if self.roi_size[self.slice_axis] == 1:\n", + " # Pass 4D input [N, C, H, W]/[N, C, D, W]/[N, C, D, H] to the model as it is 2D.\n", + " x = x.squeeze(dim=self.slice_axis + 2)\n", " out = network(x, *args, **kwargs)\n", " # Unsqueeze the network output so it is [N, C, D, H, W] as expected by the default SlidingWindowInferer class\n", - " return out.unsqueeze(dim=2)\n", + " return out.unsqueeze(dim=self.slice_axis + 2)\n", "\n", " else:\n", " return network(x, *args, **kwargs)" @@ -128,7 +136,16 @@ "execution_count": 3, "id": "85b15305", "metadata": {}, - "outputs": [], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Axial Inferer Output Shape: torch.Size([1, 1, 64, 256, 256])\n", + "Coronal Inferer Output Shape: torch.Size([1, 1, 64, 256, 256])\n" + ] + } + ], "source": [ "# Create a 2D UNet with randomly initialized weights for testing purposes\n", "from monai.networks.nets import UNet\n", @@ -144,37 +161,36 @@ ")\n", "\n", "# Initialize a dummy 3D tensor volume with shape (N,C,D,H,W)\n", - "input_volume = torch.ones(1, 1, 30, 256, 256)\n", + "input_volume = torch.ones(1, 1, 64, 256, 256)\n", "\n", - "# Create an instance of YourSlidingWindowInferer with roi_size as the 256x256 (HxW)\n", - "inferer = YourSlidingWindowInferer(roi_size=(256, 256),\n", + "# Create an instance of YourSlidingWindowInferer with roi_size as the 256x256 (HxW) and sliding over D axis\n", + "axial_inferer = YourSlidingWindowInferer(roi_size=(256, 256),\n", " sw_batch_size=1,\n", " cval=-1)\n", "\n", - "output = inferer(input_volume, net)" + "output = axial_inferer(input_volume, net)\n", + "\n", + "# Output is a 3D volume with 2D slices aggregated\n", + "print(\"Axial Inferer Output Shape: \", output.shape)\n", + "\n", + "# Create an instance of YourSlidingWindowInferer with roi_size as the 64x256 (DxW) and sliding over H axis\n", + "coronal_inferer = YourSlidingWindowInferer(roi_size=(64, 256),\n", + " sw_batch_size=1, slice_axis=1, # Slice axis is added here\n", + " cval=-1)\n", + "\n", + "output = coronal_inferer(input_volume, net)\n", + "\n", + "# Output is a 3D volume with 2D slices aggregated\n", + "print(\"Coronal Inferer Output Shape: \", output.shape)" ] }, { "cell_type": "code", - "execution_count": 4, - "id": "5ad96534", + "execution_count": null, + "id": "454c353f", "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "torch.Size([1, 1, 30, 256, 256])" - ] - }, - "execution_count": 4, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "# Output is a 3D volume with 2D slices aggregated\n", - "output.shape" - ] + "outputs": [], + "source": [] } ], "metadata": { From 252e329192707c066842d92446ffe1ab9e8ed705 Mon Sep 17 00:00:00 2001 From: Suraj Date: Tue, 14 Dec 2021 03:26:40 +0100 Subject: [PATCH 3/4] Address comments + PEP compliance Signed-off-by: Suraj --- modules/2d_inference_3d_volume.ipynb | 81 +++++++++++++--------------- 1 file changed, 37 insertions(+), 44 deletions(-) diff --git a/modules/2d_inference_3d_volume.ipynb b/modules/2d_inference_3d_volume.ipynb index 2192e74e35..bbf1bf6c06 100644 --- a/modules/2d_inference_3d_volume.ipynb +++ b/modules/2d_inference_3d_volume.ipynb @@ -35,21 +35,24 @@ "execution_count": 1, "id": "f2e1b91f", "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Requirement already satisfied: monai in /home/suraj/miniconda3/envs/monai/lib/python3.7/site-packages (0.8.0)\n", - "Requirement already satisfied: numpy>=1.17 in /home/suraj/miniconda3/envs/monai/lib/python3.7/site-packages (from monai) (1.21.4)\n", - "Requirement already satisfied: torch>=1.6 in /home/suraj/miniconda3/envs/monai/lib/python3.7/site-packages (from monai) (1.10.0)\n", - "Requirement already satisfied: typing-extensions in /home/suraj/miniconda3/envs/monai/lib/python3.7/site-packages (from torch>=1.6->monai) (4.0.1)\n" - ] - } - ], + "outputs": [], "source": [ "# Install monai\n", - "!pip install monai" + "!python -c \"import monai\" || pip install -q \"monai-weekly\"" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "e9cd1b08", + "metadata": {}, + "outputs": [], + "source": [ + "# Import libs\n", + "from monai.inferers import SlidingWindowInferer\n", + "import torch\n", + "from typing import Callable, Any\n", + "from monai.networks.nets import UNet" ] }, { @@ -63,20 +66,15 @@ }, { "cell_type": "code", - "execution_count": 2, + "execution_count": 3, "id": "01f8bfa3", "metadata": {}, "outputs": [], "source": [ - "from monai.inferers import SlidingWindowInferer\n", - "import torch\n", - "from typing import Callable, Any\n", - "\n", - "\n", "class YourSlidingWindowInferer(SlidingWindowInferer):\n", - " def __init__(self, slice_axis: int = 0, \n", - " *args, **kwargs):\n", - " # Set axis to slice the volume across, for example, `0` could slide over axial slices, `1` over coronal slices \n", + " def __init__(self, slice_axis: int = 0, *args, **kwargs):\n", + " # Set axis to slice the volume across, for example, `0` could slide over axial slices,\n", + " # `1` over coronal slices\n", " # and `2` over sagittal slices.\n", " self.slice_axis = slice_axis\n", "\n", @@ -91,7 +89,7 @@ " **kwargs: Any,\n", " ) -> torch.Tensor:\n", "\n", - " # Check if roi size (eg. 2D roi) and input volume sizes (3D input) mismatch \n", + " # Check if roi size (eg. 2D roi) and input volume sizes (3D input) mismatch\n", " if len(self.roi_size) != len(inputs.shape[2:]):\n", "\n", " # If they mismatch and roi_size is 2D add another dimension to roi size\n", @@ -99,13 +97,15 @@ " self.roi_size = list(self.roi_size)\n", " self.roi_size.insert(self.slice_axis, 1)\n", " else:\n", - " raise RuntimeError(\"Currently, only 2D `roi_size` is supported, cannot broadcast to volume. \")\n", - " \n", + " raise RuntimeError(\n", + " \"Currently, only 2D `roi_size` is supported, cannot broadcast to volume. \"\n", + " )\n", + "\n", " return super().__call__(inputs, lambda x: self.network_wrapper(network, x))\n", "\n", " def network_wrapper(self, network, x, *args, **kwargs):\n", " \"\"\"\n", - " Wrapper handles cases where inference needs to be done using \n", + " Wrapper handles cases where inference needs to be done using\n", " 2D models over 3D volume inputs.\n", " \"\"\"\n", " # If depth dim is 1 in [D, H, W] roi size, then the input is 2D and needs\n", @@ -115,7 +115,8 @@ " # Pass 4D input [N, C, H, W]/[N, C, D, W]/[N, C, D, H] to the model as it is 2D.\n", " x = x.squeeze(dim=self.slice_axis + 2)\n", " out = network(x, *args, **kwargs)\n", - " # Unsqueeze the network output so it is [N, C, D, H, W] as expected by the default SlidingWindowInferer class\n", + " # Unsqueeze the network output so it is [N, C, D, H, W] as expected by\n", + " # the default SlidingWindowInferer class\n", " return out.unsqueeze(dim=self.slice_axis + 2)\n", "\n", " else:\n", @@ -133,7 +134,7 @@ }, { "cell_type": "code", - "execution_count": 3, + "execution_count": 4, "id": "85b15305", "metadata": {}, "outputs": [ @@ -148,7 +149,6 @@ ], "source": [ "# Create a 2D UNet with randomly initialized weights for testing purposes\n", - "from monai.networks.nets import UNet\n", "\n", "# 3 layer network with down/upsampling by a factor of 2 at each layer with 2-convolution residual units\n", "net = UNet(\n", @@ -157,16 +157,14 @@ " out_channels=1,\n", " channels=(4, 8, 16),\n", " strides=(2, 2),\n", - " num_res_units=2\n", + " num_res_units=2,\n", ")\n", "\n", "# Initialize a dummy 3D tensor volume with shape (N,C,D,H,W)\n", "input_volume = torch.ones(1, 1, 64, 256, 256)\n", "\n", "# Create an instance of YourSlidingWindowInferer with roi_size as the 256x256 (HxW) and sliding over D axis\n", - "axial_inferer = YourSlidingWindowInferer(roi_size=(256, 256),\n", - " sw_batch_size=1,\n", - " cval=-1)\n", + "axial_inferer = YourSlidingWindowInferer(roi_size=(256, 256), sw_batch_size=1, cval=-1)\n", "\n", "output = axial_inferer(input_volume, net)\n", "\n", @@ -174,23 +172,18 @@ "print(\"Axial Inferer Output Shape: \", output.shape)\n", "\n", "# Create an instance of YourSlidingWindowInferer with roi_size as the 64x256 (DxW) and sliding over H axis\n", - "coronal_inferer = YourSlidingWindowInferer(roi_size=(64, 256),\n", - " sw_batch_size=1, slice_axis=1, # Slice axis is added here\n", - " cval=-1)\n", + "coronal_inferer = YourSlidingWindowInferer(\n", + " roi_size=(64, 256),\n", + " sw_batch_size=1,\n", + " slice_axis=1, # Slice axis is added here\n", + " cval=-1,\n", + ")\n", "\n", "output = coronal_inferer(input_volume, net)\n", "\n", "# Output is a 3D volume with 2D slices aggregated\n", "print(\"Coronal Inferer Output Shape: \", output.shape)" ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "454c353f", - "metadata": {}, - "outputs": [], - "source": [] } ], "metadata": { From 71d514116fd4bd3056ff583a5301e08c14a9feee Mon Sep 17 00:00:00 2001 From: Suraj Date: Thu, 16 Dec 2021 02:31:29 +0100 Subject: [PATCH 4/4] Change to spatial_dim + assert Signed-off-by: Suraj --- modules/2d_inference_3d_volume.ipynb | 21 ++++++++++++--------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/modules/2d_inference_3d_volume.ipynb b/modules/2d_inference_3d_volume.ipynb index bbf1bf6c06..99c34c4c8b 100644 --- a/modules/2d_inference_3d_volume.ipynb +++ b/modules/2d_inference_3d_volume.ipynb @@ -72,11 +72,11 @@ "outputs": [], "source": [ "class YourSlidingWindowInferer(SlidingWindowInferer):\n", - " def __init__(self, slice_axis: int = 0, *args, **kwargs):\n", - " # Set axis to slice the volume across, for example, `0` could slide over axial slices,\n", + " def __init__(self, spatial_dim: int = 0, *args, **kwargs):\n", + " # Set dim to slice the volume across, for example, `0` could slide over axial slices,\n", " # `1` over coronal slices\n", " # and `2` over sagittal slices.\n", - " self.slice_axis = slice_axis\n", + " self.spatial_dim = spatial_dim\n", "\n", " super().__init__(*args, **kwargs)\n", "\n", @@ -89,13 +89,17 @@ " **kwargs: Any,\n", " ) -> torch.Tensor:\n", "\n", + " assert (\n", + " self.spatial_dim < 3\n", + " ), \"`spatial_dim` can only be `[D, H, W]` with `0, 1, 2` respectively\"\n", + "\n", " # Check if roi size (eg. 2D roi) and input volume sizes (3D input) mismatch\n", " if len(self.roi_size) != len(inputs.shape[2:]):\n", "\n", " # If they mismatch and roi_size is 2D add another dimension to roi size\n", " if len(self.roi_size) == 2:\n", " self.roi_size = list(self.roi_size)\n", - " self.roi_size.insert(self.slice_axis, 1)\n", + " self.roi_size.insert(self.spatial_dim, 1)\n", " else:\n", " raise RuntimeError(\n", " \"Currently, only 2D `roi_size` is supported, cannot broadcast to volume. \"\n", @@ -111,13 +115,13 @@ " # If depth dim is 1 in [D, H, W] roi size, then the input is 2D and needs\n", " # be handled accordingly\n", "\n", - " if self.roi_size[self.slice_axis] == 1:\n", + " if self.roi_size[self.spatial_dim] == 1:\n", " # Pass 4D input [N, C, H, W]/[N, C, D, W]/[N, C, D, H] to the model as it is 2D.\n", - " x = x.squeeze(dim=self.slice_axis + 2)\n", + " x = x.squeeze(dim=self.spatial_dim + 2)\n", " out = network(x, *args, **kwargs)\n", " # Unsqueeze the network output so it is [N, C, D, H, W] as expected by\n", " # the default SlidingWindowInferer class\n", - " return out.unsqueeze(dim=self.slice_axis + 2)\n", + " return out.unsqueeze(dim=self.spatial_dim + 2)\n", "\n", " else:\n", " return network(x, *args, **kwargs)" @@ -170,12 +174,11 @@ "\n", "# Output is a 3D volume with 2D slices aggregated\n", "print(\"Axial Inferer Output Shape: \", output.shape)\n", - "\n", "# Create an instance of YourSlidingWindowInferer with roi_size as the 64x256 (DxW) and sliding over H axis\n", "coronal_inferer = YourSlidingWindowInferer(\n", " roi_size=(64, 256),\n", " sw_batch_size=1,\n", - " slice_axis=1, # Slice axis is added here\n", + " spatial_dim=1, # Spatial dim to slice along is added here\n", " cval=-1,\n", ")\n", "\n",