From 646ffc3a541b7fa71e7d8ccd25e72ad81da9231f Mon Sep 17 00:00:00 2001 From: Konstantinos Ntatsis Date: Thu, 25 May 2023 14:09:09 +0200 Subject: [PATCH 1/3] ENH: Bump itk-elastix version in requirements.txt --- .binder/requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.binder/requirements.txt b/.binder/requirements.txt index 47b6bddb..bc99a70d 100644 --- a/.binder/requirements.txt +++ b/.binder/requirements.txt @@ -1,4 +1,4 @@ -itk-elastix>=0.12.0 +itk-elastix>=0.17.1 itkwidgets>=0.32.0 jupyterlab>=2.2.0 imageio From 6d385d0088c45d9ba47ab24176b6c6d843414603 Mon Sep 17 00:00:00 2001 From: Konstantinos Ntatsis Date: Thu, 25 May 2023 16:32:49 +0200 Subject: [PATCH 2/3] ENH: Add notebook that discusses pixel types --- examples/ITK_Example20_PixelTypes.ipynb | 325 ++++++++++++++++++++++++ 1 file changed, 325 insertions(+) create mode 100644 examples/ITK_Example20_PixelTypes.ipynb diff --git a/examples/ITK_Example20_PixelTypes.ipynb b/examples/ITK_Example20_PixelTypes.ipynb new file mode 100644 index 00000000..f1346fa8 --- /dev/null +++ b/examples/ITK_Example20_PixelTypes.ipynb @@ -0,0 +1,325 @@ +{ + "cells": [ + { + "attachments": {}, + "cell_type": "markdown", + "id": "ae01d8cd", + "metadata": {}, + "source": [ + "# Input, output and internal pixel types" + ] + }, + { + "attachments": {}, + "cell_type": "markdown", + "id": "c0e4e8c1", + "metadata": {}, + "source": [ + "itk-elastix `v0.17.0` and higher supports various types for the input images, while the output image type is defined to match the inputs. That way the users do not need to pay special attention to convert their images before using elastix or transformix" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "id": "32d8a7a1", + "metadata": {}, + "outputs": [], + "source": [ + "import itk\n", + "import numpy as np" + ] + }, + { + "attachments": {}, + "cell_type": "markdown", + "id": "1ef001b8", + "metadata": {}, + "source": [ + "### Elastix pixel types" + ] + }, + { + "attachments": {}, + "cell_type": "markdown", + "id": "b77be03e", + "metadata": {}, + "source": [ + "In the following example, we create itk images from corresponding numpy arrays of different data types, and demonstrate that elastix is able to register them. More details on the inner workings of elastix are explained in the final section of the current notebook." + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "59e15e87", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Input images type Output image type Corresponding numpy dtype\n", + " \n", + " \n", + " \n", + " \n", + " \n" + ] + } + ], + "source": [ + "# Define a rigid parameter object\n", + "parameter_object = itk.ParameterObject.New()\n", + "default_rigid_parameter_map = parameter_object.GetDefaultParameterMap('rigid')\n", + "parameter_object.AddParameterMap(default_rigid_parameter_map)\n", + "\n", + "# Non-exhaustive list of image types supported by itk-elastix\n", + "np_dtypes = [np.uint8, np.int16, np.uint16, np.float32, np.double]\n", + "\n", + "# Function to create a random 3D itk image\n", + "def create_random_image(dtype):\n", + " arr = (100 * np.random.rand(64, 64, 64)).astype(dtype)\n", + " return itk.image_from_array(arr)\n", + "\n", + "# Loop over different dtypes\n", + "indent = 30\n", + "print(\"Input images type\".ljust(indent) + \"Output image type\".ljust(indent) + \"Corresponding numpy dtype\")\n", + "for dtype in np_dtypes:\n", + " image = create_random_image(dtype)\n", + " result_image, result_transform_parameters = itk.elastix_registration_method(\n", + " image, image,\n", + " parameter_object=parameter_object,\n", + " log_to_console=False)\n", + " \n", + " print(f\"{itk.template(image)[1][0]}\".ljust(indent) + f\"{itk.template(result_image)[1][0]}\".ljust(indent) + f\"{dtype}\")" + ] + }, + { + "attachments": {}, + "cell_type": "markdown", + "id": "1cb169fe", + "metadata": {}, + "source": [ + "### Transformix pixel types" + ] + }, + { + "attachments": {}, + "cell_type": "markdown", + "id": "7f67cfa3", + "metadata": {}, + "source": [ + "The previous example used elastix, but the same functionality works also for transformix" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "0548b67e", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Input images type Output image type Corresponding numpy dtype\n", + " \n", + " \n", + " \n", + " \n", + " \n" + ] + } + ], + "source": [ + "print(\"Input images type\".ljust(indent) + \"Output image type\".ljust(indent) + \"Corresponding numpy dtype\")\n", + "for dtype in np_dtypes:\n", + " image = create_random_image(dtype)\n", + " transformed_image = itk.transformix_filter(\n", + " image,\n", + " transform_parameter_object=result_transform_parameters,\n", + " log_to_console=False)\n", + " \n", + " print(f\"{itk.template(image)[1][0]}\".ljust(indent) + f\"{itk.template(transformed_image)[1][0]}\".ljust(indent) + f\"{dtype}\")" + ] + }, + { + "attachments": {}, + "cell_type": "markdown", + "id": "6935081a", + "metadata": {}, + "source": [ + "### Internal pixel types" + ] + }, + { + "attachments": {}, + "cell_type": "markdown", + "id": "59c0389f", + "metadata": {}, + "source": [ + "Under the hood, elastix/transformix will [cast](https://itk.org/Doxygen/html/classitk_1_1CastImageFilter.html) the pixels of the input image(s) to an internal pixel type. By default, the internal pixel type is `float`, but for 3D and 4D images, it can be `short`, according to the value of the parameters `FixedInternalPixelType` and `MovingInternalPixelType` specified in the parameter object. When the registration or transformation is finished, the image will be converted back (cast) to original pixel type before it is output to the user." + ] + }, + { + "attachments": {}, + "cell_type": "markdown", + "id": "62864e1b", + "metadata": {}, + "source": [ + "Let's do a simple test to show that actually choosing `short` as internal pixel type will truncate the floating values of the pixels:" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "69c5eeb5", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "ParameterObject (000002CA822664B0)\n", + " RTTI typeinfo: class elastix::ParameterObject\n", + " Reference Count: 1\n", + " Modified Time: 173699\n", + " Debug: Off\n", + " Object Name: \n", + " Observers: \n", + " none\n", + "ParameterMap 0: \n", + " (CenterOfRotationPoint 31.5 31.5 31.5)\n", + " (CompressResultImage \"false\")\n", + " (ComputeZYX \"false\")\n", + " (DefaultPixelValue 0)\n", + " (Direction 1 0 0 0 1 0 0 0 1)\n", + " (FinalBSplineInterpolationOrder 1)\n", + " (FixedImageDimension 3)\n", + " (FixedInternalImagePixelType \"float\")\n", + " (HowToCombineTransforms \"Compose\")\n", + " (Index 0 0 0)\n", + " (InitialTransformParametersFileName \"NoInitialTransform\")\n", + " (MovingImageDimension 3)\n", + " (MovingInternalImagePixelType \"float\")\n", + " (NumberOfParameters 6)\n", + " (Origin 0 0 0)\n", + " (ResampleInterpolator \"FinalBSplineInterpolator\")\n", + " (Resampler \"DefaultResampler\")\n", + " (ResultImageFormat \"nii\")\n", + " (ResultImagePixelType \"double\")\n", + " (Size 64 64 64)\n", + " (Spacing 1 1 1)\n", + " (Transform \"EulerTransform\")\n", + " (TransformParameters 0 0 0 0 0 0)\n", + " (UseDirectionCosines \"true\")\n", + "\n" + ] + } + ], + "source": [ + "# Copy the previous transformation result to two new parameter objects\n", + "transform_parameters_float = itk.ParameterObject.New()\n", + "transform_parameters_short = itk.ParameterObject.New()\n", + "\n", + "transform_parameters_float.AddParameterMap(result_transform_parameters.GetParameterMap(0))\n", + "transform_parameters_short.AddParameterMap(result_transform_parameters.GetParameterMap(0))\n", + "\n", + "# Switch to linear interpolation for resampling\n", + "transform_parameters_float.SetParameter(0, 'FinalBSplineInterpolationOrder', '1')\n", + "transform_parameters_short.SetParameter(0, 'FinalBSplineInterpolationOrder', '1')\n", + "\n", + "# Set transform to zero so that the input image matches the output image\n", + "transform_parameters_float.SetParameter(0, 'TransformParameters', 6 * ['0']) # 6 transformation parameters to zero\n", + "transform_parameters_short.SetParameter(0, 'TransformParameters', 6 * ['0'])\n", + "\n", + "print(transform_parameters_float)" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "id": "7d610c09", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "For float internal pixel type - Diff min: 0.0 | max: 0.0\n", + "For short internal pixel type - Diff min: -0.9999923706054688 | max: -6.811788807681296e-06\n" + ] + } + ], + "source": [ + "# Create the input image as float\n", + "image = create_random_image(np.float32)\n", + "\n", + "# Transform the image using 'float' internal pixel type\n", + "transformed_image_float = itk.transformix_filter(image,\n", + " transform_parameter_object=transform_parameters_float,\n", + " )\n", + "\n", + "# Transform the image using 'short' internal pixel type\n", + "transform_parameters_short.SetParameter(0, 'FixedInternalImagePixelType', 'short')\n", + "transform_parameters_short.SetParameter(0, 'MovingInternalImagePixelType', 'short')\n", + "transformed_image_short = itk.transformix_filter(image,\n", + " transform_parameter_object=transform_parameters_short,\n", + " )\n", + "\n", + "# Compute the differences between the result and the input image\n", + "diff_float = transformed_image_float[:] - image[:]\n", + "diff_short = transformed_image_short[:] - image[:]\n", + "\n", + "print(f\"For float internal pixel type - Diff min: {diff_float.min()} | max: {diff_float.max()}\")\n", + "print(f\"For short internal pixel type - Diff min: {diff_short.min()} | max: {diff_short.max()}\")" + ] + }, + { + "attachments": {}, + "cell_type": "markdown", + "id": "d40cf32a", + "metadata": {}, + "source": [ + "We observe that difference between input and output images is zero. On the other hand, for the case of `short` internal type we see that there is a difference that corresponds to the rounding error (truncation of the floating part). " + ] + }, + { + "attachments": {}, + "cell_type": "markdown", + "id": "f675ed9e", + "metadata": {}, + "source": [ + "Note: Since specifying the internal pixel type to short is meant as a way to reduce the memory requirements for the registration, elastix supports it by default only for `>=3D` images. There is a CMake option to enable support for `short` with `2D` images when building elastix on your own." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "81afe524", + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "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.9.0" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} From 0a60fc5eb2045c60e7d168764bce0d8fa9db7b2a Mon Sep 17 00:00:00 2001 From: Konstantinos Ntatsis Date: Thu, 25 May 2023 18:10:40 +0200 Subject: [PATCH 3/3] ENH: Read masks as uchar in notebook --- ...mple09_PointSetAndMaskTransformation.ipynb | 90 ++++++++++++++----- 1 file changed, 70 insertions(+), 20 deletions(-) diff --git a/examples/ITK_Example09_PointSetAndMaskTransformation.ipynb b/examples/ITK_Example09_PointSetAndMaskTransformation.ipynb index 320d6078..6fb1058b 100644 --- a/examples/ITK_Example09_PointSetAndMaskTransformation.ipynb +++ b/examples/ITK_Example09_PointSetAndMaskTransformation.ipynb @@ -1,6 +1,7 @@ { "cells": [ { + "attachments": {}, "cell_type": "markdown", "metadata": {}, "source": [ @@ -8,6 +9,7 @@ ] }, { + "attachments": {}, "cell_type": "markdown", "metadata": {}, "source": [ @@ -19,6 +21,7 @@ ] }, { + "attachments": {}, "cell_type": "markdown", "metadata": {}, "source": [ @@ -57,6 +60,7 @@ ] }, { + "attachments": {}, "cell_type": "markdown", "metadata": {}, "source": [ @@ -65,7 +69,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 3, "metadata": {}, "outputs": [], "source": [ @@ -77,6 +81,7 @@ ] }, { + "attachments": {}, "cell_type": "markdown", "metadata": {}, "source": [ @@ -85,25 +90,27 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 4, "metadata": {}, "outputs": [], "source": [ "# Import Mask\n", - "moving_mask = itk.imread('data/CT_3D_lung_moving_mask.mha', itk.F)" + "# Note: Version v0.17.0 and onwards supports various image types for elastix and transformix functions/objects\n", + "moving_mask = itk.imread('data/CT_3D_lung_moving_mask.mha', itk.UC)" ] }, { "cell_type": "code", - "execution_count": null, + "execution_count": 5, "metadata": {}, "outputs": [], "source": [ "# Mask image is a binary image, therefore the FinalBSplineInterpolationOrder should be 0\n", - "result_transform_parameters.SetParameter('FinalBSplineInterpolationOrder','0')\n" + "result_transform_parameters.SetParameter('FinalBSplineInterpolationOrder','0')" ] }, { + "attachments": {}, "cell_type": "markdown", "metadata": {}, "source": [ @@ -112,7 +119,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 6, "metadata": {}, "outputs": [], "source": [ @@ -123,6 +130,7 @@ ] }, { + "attachments": {}, "cell_type": "markdown", "metadata": {}, "source": [ @@ -131,7 +139,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 7, "metadata": {}, "outputs": [], "source": [ @@ -148,6 +156,7 @@ ] }, { + "attachments": {}, "cell_type": "markdown", "metadata": {}, "source": [ @@ -155,6 +164,7 @@ ] }, { + "attachments": {}, "cell_type": "markdown", "metadata": {}, "source": [ @@ -163,7 +173,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 8, "metadata": {}, "outputs": [], "source": [ @@ -180,12 +190,12 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 9, "metadata": {}, "outputs": [], "source": [ "# Import groundtruth segmentation\n", - "fixed_mask = itk.imread('data/CT_3D_lung_fixed_mask.mha', itk.F)\n", + "fixed_mask = itk.imread('data/CT_3D_lung_fixed_mask.mha', itk.UC)\n", "\n", "# Cast itk images to numpy arrays and round result image to boolean array.\n", "fixed_mask_np = np.asarray(fixed_mask)\n", @@ -194,23 +204,47 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 10, "metadata": {}, - "outputs": [], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Dice loss: 0.9862246731348647\n" + ] + } + ], "source": [ "print(\"Dice loss:\",dice_loss(fixed_mask_np, result_mask_np))" ] }, { "cell_type": "code", - "execution_count": null, + "execution_count": 11, "metadata": {}, - "outputs": [], + "outputs": [ + { + "data": { + "application/vnd.jupyter.widget-view+json": { + "model_id": "c76e48b027c84e37963cf1e02ee8a702", + "version_major": 2, + "version_minor": 0 + }, + "text/plain": [ + "AppLayout(children=(HBox(children=(Label(value='Link:'), Checkbox(value=False, description='cmap'), Checkbox(v…" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], "source": [ "compare(fixed_mask, result_moving_mask)" ] }, { + "attachments": {}, "cell_type": "markdown", "metadata": {}, "source": [ @@ -219,7 +253,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 12, "metadata": {}, "outputs": [], "source": [ @@ -231,6 +265,7 @@ ] }, { + "attachments": {}, "cell_type": "markdown", "metadata": {}, "source": [ @@ -239,7 +274,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 13, "metadata": {}, "outputs": [], "source": [ @@ -262,9 +297,24 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 14, "metadata": {}, - "outputs": [], + "outputs": [ + { + "data": { + "application/vnd.jupyter.widget-view+json": { + "model_id": "3a044dd051e04f2f92d3dae77639590e", + "version_major": 2, + "version_minor": 0 + }, + "text/plain": [ + "Viewer(geometries=[], gradient_opacity=0.22, point_set_colors=array([[0.8392157, 0. , 0. ]], dtype…" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], "source": [ "view(moving_image, point_sets=[result_point_set])" ] @@ -272,7 +322,7 @@ ], "metadata": { "kernelspec": { - "display_name": "Python 3", + "display_name": "Python 3 (ipykernel)", "language": "python", "name": "python3" }, @@ -286,7 +336,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.8.0" + "version": "3.9.0" } }, "nbformat": 4,